@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.
- package/CHANGELOG.md +621 -614
- package/dist/components/AnimatedHeight.d.ts +1 -1
- package/dist/components/AnimatedHeight.js +26 -26
- package/dist/components/AsyncContent.d.ts +1 -1
- package/dist/components/AsyncContent.js +1 -1
- package/dist/components/BannerWarning.d.ts +1 -1
- package/dist/components/BannerWarning.js +1 -1
- package/dist/components/Breadcrumb/index.d.ts +2 -2
- package/dist/components/Breadcrumb/index.js +1 -1
- package/dist/components/Breadcrumb/styled.js +31 -31
- package/dist/components/ButtonLoading.d.ts +1 -1
- package/dist/components/ButtonLoading.js +1 -1
- package/dist/components/ChatBot.d.ts +1 -1
- package/dist/components/ChatBot.js +1 -1
- package/dist/components/ContentValidateFilter.d.ts +1 -1
- package/dist/components/ContentValidateFilter.js +1 -1
- package/dist/components/FadingOverflow.d.ts +1 -1
- package/dist/components/FadingOverflow.js +69 -69
- package/dist/components/FileTreeView/More.d.ts +1 -1
- package/dist/components/FileTreeView/More.js +1 -1
- package/dist/components/FileTreeView/index.d.ts +1 -1
- package/dist/components/FileTreeView/index.js +1 -1
- package/dist/components/InfiniteScroll.d.ts +1 -1
- package/dist/components/InfiniteScroll.js +1 -1
- package/dist/components/InfoMaintenanceBanner.d.ts +1 -1
- package/dist/components/InfoMaintenanceBanner.js +2 -2
- package/dist/components/LazyMarkdown/BlockquoteMd.d.ts +1 -1
- package/dist/components/LazyMarkdown/BlockquoteMd.js +1 -1
- package/dist/components/LazyMarkdown/CodeViewer.d.ts +1 -1
- package/dist/components/LazyMarkdown/CodeViewer.js +76 -76
- package/dist/components/LazyMarkdown/Markdown.d.ts +1 -1
- package/dist/components/LazyMarkdown/Markdown.js +1 -1
- package/dist/components/LazyMarkdown/MarkdownButton.d.ts +1 -1
- package/dist/components/LazyMarkdown/MarkdownButton.js +1 -1
- package/dist/components/LazyMarkdown/Video.d.ts +1 -1
- package/dist/components/LazyMarkdown/Video.js +1 -1
- package/dist/components/LazyMarkdown/index.d.ts +1 -1
- package/dist/components/LazyMarkdown/index.js +1 -1
- package/dist/components/Placeholder.d.ts +3 -3
- package/dist/components/Placeholder.js +1 -1
- package/dist/components/ScrollView.js +16 -16
- package/dist/components/Select/BadgeItem.d.ts +1 -1
- package/dist/components/Select/BadgeItem.js +1 -1
- package/dist/components/Select/ClearInput.d.ts +1 -1
- package/dist/components/Select/ClearInput.js +1 -1
- package/dist/components/Select/CloseItem.d.ts +1 -1
- package/dist/components/Select/CloseItem.js +1 -1
- package/dist/components/Select/CreatableSelect.js +1 -1
- package/dist/components/Select/CustomMenu.d.ts +1 -1
- package/dist/components/Select/CustomMenu.js +1 -1
- package/dist/components/Select/LabelItem.d.ts +1 -1
- package/dist/components/Select/LabelItem.js +1 -1
- package/dist/components/Select/MultiValue.d.ts +1 -1
- package/dist/components/Select/MultiValue.js +1 -1
- package/dist/components/Select/SelectInfiniteScroll.d.ts +1 -1
- package/dist/components/Select/SelectInfiniteScroll.js +1 -1
- package/dist/components/Select/SelectSearch.d.ts +1 -1
- package/dist/components/Select/SelectSearch.js +1 -1
- package/dist/components/SelectionList.d.ts +1 -1
- package/dist/components/SelectionList.js +61 -61
- package/dist/components/StatusCircle.d.ts +1 -1
- package/dist/components/StatusCircle.js +6 -6
- package/dist/components/Stepper/Navigation.js +4 -4
- package/dist/components/Stepper/Step.js +3 -3
- package/dist/components/Stepper/Stepper.js +6 -6
- package/dist/components/Stepper/headers.js +22 -22
- package/dist/components/Table/HeaderItem.js +1 -1
- package/dist/components/Table/SettingsVerticalMenu.d.ts +1 -1
- package/dist/components/Table/SettingsVerticalMenu.js +1 -1
- package/dist/components/Table/StyledLinkTable.d.ts +1 -1
- package/dist/components/Table/StyledLinkTable.js +5 -5
- package/dist/components/Table/TableData.d.ts +1 -1
- package/dist/components/Table/TableData.js +25 -25
- package/dist/components/TimelineSection.d.ts +1 -1
- package/dist/components/TimelineSection.js +14 -14
- package/dist/components/error/ErrorFeedback.d.ts +1 -1
- package/dist/components/error/ErrorFeedback.js +35 -35
- package/dist/components/error/NotFound.d.ts +1 -1
- package/dist/components/error/NotFound.js +1 -1
- package/dist/components/error/UnderMaintenance.d.ts +1 -1
- package/dist/components/error/UnderMaintenance.js +1 -1
- package/dist/components/form/Form/Form.d.ts +1 -1
- package/dist/components/form/Form/Form.js +1 -1
- package/dist/components/form/Form/FormGroup.d.ts +2 -2
- package/dist/components/form/Form/FormGroup.js +1 -1
- package/dist/components/form/SearchInput.d.ts +1 -1
- package/dist/components/form/SearchInput.js +1 -1
- package/dist/components/form/Select/CustomSelect.d.ts +1 -1
- package/dist/components/form/Select/CustomSelect.js +1 -1
- package/dist/components/form/Select/DetailedSelect.d.ts +1 -1
- package/dist/components/form/Select/DetailedSelect.js +1 -1
- package/dist/components/form/Select/Select.d.ts +1 -1
- package/dist/components/form/Select/Select.js +1 -1
- package/dist/components/form/Select/styled.js +161 -161
- package/dist/components/form/Select/utils.js +1 -1
- package/dist/components/notification/NotificationComponent.d.ts +1 -1
- package/dist/components/notification/NotificationComponent.js +54 -54
- package/dist/components/notification/NotificationItem.d.ts +1 -1
- package/dist/components/notification/NotificationItem.js +1 -1
- package/dist/components/notification/NotificationList.d.ts +1 -1
- package/dist/components/notification/NotificationList.js +43 -43
- package/dist/components/notification/NotificationPlaceholder.d.ts +1 -1
- package/dist/components/notification/NotificationPlaceholder.js +9 -9
- package/dist/components/notification/NotificationPlaceholder.js.map +1 -1
- package/dist/containers/NotificationsPage.d.ts +1 -1
- package/dist/containers/NotificationsPage.js +10 -10
- package/dist/context/anchor.d.ts +1 -1
- package/dist/context/anchor.js +1 -1
- package/dist/context/loading.d.ts +1 -1
- package/dist/context/loading.js +1 -1
- package/dist/context/notification/context.d.ts +1 -1
- package/dist/context/notification/context.js +1 -1
- package/dist/hooks/date.js +1 -1
- package/dist/hooks/service-now.js +28 -28
- package/dist/svg/AI.d.ts +1 -1
- package/dist/svg/AI.js +1 -1
- package/dist/svg/CS.d.ts +1 -1
- package/dist/svg/CS.js +1 -1
- package/dist/svg/EDP.d.ts +1 -1
- package/dist/svg/EDP.js +1 -1
- package/dist/svg/Forbidden.d.ts +1 -1
- package/dist/svg/Forbidden.js +1 -1
- package/dist/svg/GenericPlaceholder.d.ts +1 -1
- package/dist/svg/GenericPlaceholder.js +1 -1
- package/dist/svg/HUB.d.ts +1 -1
- package/dist/svg/HUB.js +1 -1
- package/dist/svg/Logo.d.ts +1 -1
- package/dist/svg/Logo.js +1 -1
- package/dist/svg/MiniLogo.d.ts +1 -1
- package/dist/svg/MiniLogo.js +1 -1
- package/dist/svg/NotFound.d.ts +1 -1
- package/dist/svg/NotFound.js +1 -1
- package/dist/svg/ServerError.d.ts +1 -1
- package/dist/svg/ServerError.js +1 -1
- package/dist/svg/Unauthenticated.d.ts +1 -1
- package/dist/svg/Unauthenticated.js +1 -1
- package/package.json +6 -6
- package/readme.md +66 -66
- package/src/components/AnimatedHeight.tsx +174 -174
- package/src/components/AsyncContent.tsx +78 -78
- package/src/components/BannerWarning.tsx +91 -91
- package/src/components/Breadcrumb/index.tsx +76 -76
- package/src/components/Breadcrumb/styled.ts +37 -37
- package/src/components/ButtonLoading.tsx +29 -29
- package/src/components/ChatBot.tsx +82 -82
- package/src/components/ContentValidateFilter.tsx +15 -15
- package/src/components/FadingOverflow.tsx +265 -265
- package/src/components/FileTreeView/More.tsx +114 -114
- package/src/components/FileTreeView/index.tsx +186 -186
- package/src/components/InfiniteScroll.tsx +24 -24
- package/src/components/InfoMaintenanceBanner.tsx +29 -29
- package/src/components/LazyMarkdown/BlockquoteMd.tsx +107 -107
- package/src/components/LazyMarkdown/CodeViewer.tsx +161 -161
- package/src/components/LazyMarkdown/Markdown.tsx +122 -122
- package/src/components/LazyMarkdown/MarkdownButton.tsx +24 -24
- package/src/components/LazyMarkdown/Video.tsx +13 -13
- package/src/components/LazyMarkdown/index.tsx +21 -21
- package/src/components/Placeholder.tsx +118 -118
- package/src/components/ScrollView.tsx +57 -57
- package/src/components/Select/BadgeItem.tsx +58 -58
- package/src/components/Select/ClearInput.tsx +24 -24
- package/src/components/Select/CloseItem.tsx +38 -38
- package/src/components/Select/CreatableSelect.tsx +155 -155
- package/src/components/Select/CustomMenu.tsx +16 -16
- package/src/components/Select/LabelItem.tsx +8 -8
- package/src/components/Select/MultiValue.tsx +49 -49
- package/src/components/Select/SelectInfiniteScroll.tsx +82 -82
- package/src/components/Select/SelectSearch.tsx +195 -195
- package/src/components/Select/index.tsx +7 -7
- package/src/components/Select/types.ts +8 -8
- package/src/components/SelectionList.tsx +427 -427
- package/src/components/StatusCircle.tsx +67 -67
- package/src/components/Stepper/Navigation.tsx +97 -97
- package/src/components/Stepper/Step.tsx +30 -30
- package/src/components/Stepper/Stepper.tsx +113 -113
- package/src/components/Stepper/headers.tsx +64 -64
- package/src/components/Stepper/index.ts +3 -3
- package/src/components/Table/HeaderItem.tsx +52 -52
- package/src/components/Table/SettingsVerticalMenu.tsx +50 -50
- package/src/components/Table/StyledLinkTable.tsx +22 -22
- package/src/components/Table/TableData.tsx +251 -251
- package/src/components/Table/index.tsx +2 -2
- package/src/components/TimelineSection.tsx +66 -66
- package/src/components/error/ErrorFeedback.tsx +217 -217
- package/src/components/error/NotFound.tsx +24 -24
- package/src/components/error/UnderMaintenance.tsx +30 -30
- package/src/components/error/index.ts +4 -4
- package/src/components/form/Form/Form.tsx +101 -101
- package/src/components/form/Form/FormGroup.tsx +221 -221
- package/src/components/form/Form/index.ts +2 -2
- package/src/components/form/SearchInput.tsx +69 -69
- package/src/components/form/Select/CustomSelect.tsx +232 -232
- package/src/components/form/Select/DetailedSelect.tsx +85 -85
- package/src/components/form/Select/Select.tsx +67 -67
- package/src/components/form/Select/index.ts +4 -4
- package/src/components/form/Select/styled.ts +165 -165
- package/src/components/form/Select/types.ts +112 -112
- package/src/components/form/Select/utils.tsx +28 -28
- package/src/components/notification/NotificationComponent.tsx +340 -340
- package/src/components/notification/NotificationItem.tsx +336 -336
- package/src/components/notification/NotificationList.tsx +178 -178
- package/src/components/notification/NotificationPlaceholder.tsx +43 -43
- package/src/components/notification/types.ts +72 -72
- package/src/containers/NotificationsPage.tsx +98 -98
- package/src/context/anchor.tsx +37 -37
- package/src/context/loading.tsx +36 -36
- package/src/context/notification/LazyNotificationList.ts +103 -103
- package/src/context/notification/NotificationController.ts +104 -104
- package/src/context/notification/context.tsx +23 -23
- package/src/context/notification/hooks.ts +98 -98
- package/src/context/notification/types.ts +65 -65
- package/src/hooks/date.ts +31 -31
- package/src/hooks/keyboard.tsx +128 -128
- package/src/hooks/manual-render.tsx +10 -10
- package/src/hooks/service-now.tsx +233 -233
- package/src/hooks/text.tsx +30 -30
- package/src/hooks/title.tsx +28 -28
- package/src/hooks/use-effect-once.tsx +43 -43
- package/src/index.ts +19 -19
- package/src/notifications.ts +11 -11
- package/src/svg/AI.tsx +41 -41
- package/src/svg/CS.tsx +48 -48
- package/src/svg/EDP.tsx +31 -31
- package/src/svg/Forbidden.tsx +22 -22
- package/src/svg/GenericPlaceholder.tsx +20 -20
- package/src/svg/HUB.tsx +48 -48
- package/src/svg/Logo.tsx +16 -16
- package/src/svg/MiniLogo.tsx +12 -12
- package/src/svg/NotFound.tsx +16 -16
- package/src/svg/ServerError.tsx +33 -33
- package/src/svg/Unauthenticated.tsx +16 -16
- package/src/svg/index.ts +11 -11
- package/src/utils/accessibility.ts +135 -135
- package/src/utils/cookie.ts +73 -73
- package/src/utils/promise.ts +5 -5
- package/src/utils/read-file.ts +16 -16
- 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
|
+
}
|