@stack-spot/portal-components 2.27.1 → 2.27.2

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 (246) hide show
  1. package/CHANGELOG.md +635 -628
  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 +7 -3
  40. package/dist/components/Placeholder.d.ts.map +1 -1
  41. package/dist/components/Placeholder.js +3 -3
  42. package/dist/components/Placeholder.js.map +1 -1
  43. package/dist/components/ScrollView.js +16 -16
  44. package/dist/components/Select/BadgeItem.d.ts +1 -1
  45. package/dist/components/Select/BadgeItem.js +1 -1
  46. package/dist/components/Select/ClearInput.d.ts +1 -1
  47. package/dist/components/Select/ClearInput.js +1 -1
  48. package/dist/components/Select/CloseItem.d.ts +1 -1
  49. package/dist/components/Select/CloseItem.js +1 -1
  50. package/dist/components/Select/CreatableSelect.js +1 -1
  51. package/dist/components/Select/CustomMenu.d.ts +1 -1
  52. package/dist/components/Select/CustomMenu.js +1 -1
  53. package/dist/components/Select/LabelItem.d.ts +1 -1
  54. package/dist/components/Select/LabelItem.js +1 -1
  55. package/dist/components/Select/MultiValue.d.ts +1 -1
  56. package/dist/components/Select/MultiValue.js +1 -1
  57. package/dist/components/Select/SelectInfiniteScroll.d.ts +1 -1
  58. package/dist/components/Select/SelectInfiniteScroll.js +1 -1
  59. package/dist/components/Select/SelectSearch.d.ts +1 -1
  60. package/dist/components/Select/SelectSearch.js +1 -1
  61. package/dist/components/SelectionList.d.ts +1 -1
  62. package/dist/components/SelectionList.js +61 -61
  63. package/dist/components/StatusCircle.d.ts +1 -1
  64. package/dist/components/StatusCircle.js +6 -6
  65. package/dist/components/Stepper/Navigation.js +4 -4
  66. package/dist/components/Stepper/Step.js +3 -3
  67. package/dist/components/Stepper/Stepper.js +6 -6
  68. package/dist/components/Stepper/headers.js +22 -22
  69. package/dist/components/Table/HeaderItem.js +1 -1
  70. package/dist/components/Table/SettingsVerticalMenu.d.ts +1 -1
  71. package/dist/components/Table/SettingsVerticalMenu.js +1 -1
  72. package/dist/components/Table/StyledLinkTable.d.ts +1 -1
  73. package/dist/components/Table/StyledLinkTable.js +5 -5
  74. package/dist/components/Table/TableData.d.ts +1 -1
  75. package/dist/components/Table/TableData.js +25 -25
  76. package/dist/components/TimelineSection.d.ts +1 -1
  77. package/dist/components/TimelineSection.js +14 -14
  78. package/dist/components/error/ErrorFeedback.d.ts +1 -1
  79. package/dist/components/error/ErrorFeedback.js +35 -35
  80. package/dist/components/error/NotFound.d.ts +1 -1
  81. package/dist/components/error/NotFound.js +1 -1
  82. package/dist/components/error/UnderMaintenance.d.ts +1 -1
  83. package/dist/components/error/UnderMaintenance.js +1 -1
  84. package/dist/components/form/Form/Form.d.ts +1 -1
  85. package/dist/components/form/Form/Form.js +1 -1
  86. package/dist/components/form/Form/FormGroup.d.ts +2 -2
  87. package/dist/components/form/Form/FormGroup.js +1 -1
  88. package/dist/components/form/SearchInput.d.ts +1 -1
  89. package/dist/components/form/SearchInput.js +1 -1
  90. package/dist/components/form/Select/CustomSelect.d.ts +1 -1
  91. package/dist/components/form/Select/CustomSelect.js +1 -1
  92. package/dist/components/form/Select/DetailedSelect.d.ts +1 -1
  93. package/dist/components/form/Select/DetailedSelect.js +1 -1
  94. package/dist/components/form/Select/Select.d.ts +1 -1
  95. package/dist/components/form/Select/Select.js +1 -1
  96. package/dist/components/form/Select/styled.js +161 -161
  97. package/dist/components/form/Select/utils.js +1 -1
  98. package/dist/components/notification/NotificationComponent.d.ts +1 -1
  99. package/dist/components/notification/NotificationComponent.js +54 -54
  100. package/dist/components/notification/NotificationItem.d.ts +1 -1
  101. package/dist/components/notification/NotificationItem.d.ts.map +1 -1
  102. package/dist/components/notification/NotificationItem.js +8 -2
  103. package/dist/components/notification/NotificationItem.js.map +1 -1
  104. package/dist/components/notification/NotificationList.d.ts +1 -1
  105. package/dist/components/notification/NotificationList.d.ts.map +1 -1
  106. package/dist/components/notification/NotificationList.js +44 -44
  107. package/dist/components/notification/NotificationList.js.map +1 -1
  108. package/dist/components/notification/NotificationPlaceholder.d.ts +1 -1
  109. package/dist/components/notification/NotificationPlaceholder.d.ts.map +1 -1
  110. package/dist/components/notification/NotificationPlaceholder.js +2 -2
  111. package/dist/components/notification/NotificationPlaceholder.js.map +1 -1
  112. package/dist/containers/NotificationsPage.d.ts +1 -1
  113. package/dist/containers/NotificationsPage.js +10 -10
  114. package/dist/context/anchor.d.ts +1 -1
  115. package/dist/context/anchor.js +1 -1
  116. package/dist/context/loading.d.ts +1 -1
  117. package/dist/context/loading.js +1 -1
  118. package/dist/context/notification/context.d.ts +1 -1
  119. package/dist/context/notification/context.js +1 -1
  120. package/dist/hooks/date.js +1 -1
  121. package/dist/hooks/service-now.js +28 -28
  122. package/dist/svg/AI.d.ts +1 -1
  123. package/dist/svg/AI.js +1 -1
  124. package/dist/svg/CS.d.ts +1 -1
  125. package/dist/svg/CS.js +1 -1
  126. package/dist/svg/EDP.d.ts +1 -1
  127. package/dist/svg/EDP.js +1 -1
  128. package/dist/svg/Forbidden.d.ts +1 -1
  129. package/dist/svg/Forbidden.js +1 -1
  130. package/dist/svg/GenericPlaceholder.d.ts +4 -2
  131. package/dist/svg/GenericPlaceholder.d.ts.map +1 -1
  132. package/dist/svg/GenericPlaceholder.js +2 -2
  133. package/dist/svg/GenericPlaceholder.js.map +1 -1
  134. package/dist/svg/HUB.d.ts +1 -1
  135. package/dist/svg/HUB.js +1 -1
  136. package/dist/svg/Logo.d.ts +1 -1
  137. package/dist/svg/Logo.js +1 -1
  138. package/dist/svg/MiniLogo.d.ts +1 -1
  139. package/dist/svg/MiniLogo.js +1 -1
  140. package/dist/svg/NotFound.d.ts +1 -1
  141. package/dist/svg/NotFound.js +1 -1
  142. package/dist/svg/ServerError.d.ts +1 -1
  143. package/dist/svg/ServerError.js +1 -1
  144. package/dist/svg/Unauthenticated.d.ts +1 -1
  145. package/dist/svg/Unauthenticated.js +1 -1
  146. package/package.json +80 -80
  147. package/readme.md +66 -66
  148. package/src/components/AnimatedHeight.tsx +174 -174
  149. package/src/components/AsyncContent.tsx +78 -78
  150. package/src/components/BannerWarning.tsx +91 -91
  151. package/src/components/Breadcrumb/index.tsx +76 -76
  152. package/src/components/Breadcrumb/styled.ts +37 -37
  153. package/src/components/ButtonLoading.tsx +29 -29
  154. package/src/components/ChatBot.tsx +82 -82
  155. package/src/components/ContentValidateFilter.tsx +15 -15
  156. package/src/components/FadingOverflow.tsx +265 -265
  157. package/src/components/FileTreeView/More.tsx +114 -114
  158. package/src/components/FileTreeView/index.tsx +186 -186
  159. package/src/components/InfiniteScroll.tsx +24 -24
  160. package/src/components/InfoMaintenanceBanner.tsx +29 -29
  161. package/src/components/LazyMarkdown/BlockquoteMd.tsx +107 -107
  162. package/src/components/LazyMarkdown/CodeViewer.tsx +161 -161
  163. package/src/components/LazyMarkdown/Markdown.tsx +122 -122
  164. package/src/components/LazyMarkdown/MarkdownButton.tsx +24 -24
  165. package/src/components/LazyMarkdown/Video.tsx +13 -13
  166. package/src/components/LazyMarkdown/index.tsx +21 -21
  167. package/src/components/Placeholder.tsx +123 -118
  168. package/src/components/ScrollView.tsx +57 -57
  169. package/src/components/Select/BadgeItem.tsx +58 -58
  170. package/src/components/Select/ClearInput.tsx +24 -24
  171. package/src/components/Select/CloseItem.tsx +38 -38
  172. package/src/components/Select/CreatableSelect.tsx +155 -155
  173. package/src/components/Select/CustomMenu.tsx +16 -16
  174. package/src/components/Select/LabelItem.tsx +8 -8
  175. package/src/components/Select/MultiValue.tsx +49 -49
  176. package/src/components/Select/SelectInfiniteScroll.tsx +82 -82
  177. package/src/components/Select/SelectSearch.tsx +195 -195
  178. package/src/components/Select/index.tsx +7 -7
  179. package/src/components/Select/types.ts +8 -8
  180. package/src/components/SelectionList.tsx +427 -427
  181. package/src/components/StatusCircle.tsx +67 -67
  182. package/src/components/Stepper/Navigation.tsx +97 -97
  183. package/src/components/Stepper/Step.tsx +30 -30
  184. package/src/components/Stepper/Stepper.tsx +113 -113
  185. package/src/components/Stepper/headers.tsx +64 -64
  186. package/src/components/Stepper/index.ts +3 -3
  187. package/src/components/Table/HeaderItem.tsx +52 -52
  188. package/src/components/Table/SettingsVerticalMenu.tsx +50 -50
  189. package/src/components/Table/StyledLinkTable.tsx +22 -22
  190. package/src/components/Table/TableData.tsx +251 -251
  191. package/src/components/Table/index.tsx +2 -2
  192. package/src/components/TimelineSection.tsx +66 -66
  193. package/src/components/error/ErrorFeedback.tsx +217 -217
  194. package/src/components/error/NotFound.tsx +24 -24
  195. package/src/components/error/UnderMaintenance.tsx +30 -30
  196. package/src/components/error/index.ts +4 -4
  197. package/src/components/form/Form/Form.tsx +101 -101
  198. package/src/components/form/Form/FormGroup.tsx +221 -221
  199. package/src/components/form/Form/index.ts +2 -2
  200. package/src/components/form/SearchInput.tsx +69 -69
  201. package/src/components/form/Select/CustomSelect.tsx +232 -232
  202. package/src/components/form/Select/DetailedSelect.tsx +85 -85
  203. package/src/components/form/Select/Select.tsx +67 -67
  204. package/src/components/form/Select/index.ts +4 -4
  205. package/src/components/form/Select/styled.ts +165 -165
  206. package/src/components/form/Select/types.ts +112 -112
  207. package/src/components/form/Select/utils.tsx +28 -28
  208. package/src/components/notification/NotificationComponent.tsx +340 -340
  209. package/src/components/notification/NotificationItem.tsx +345 -337
  210. package/src/components/notification/NotificationList.tsx +179 -178
  211. package/src/components/notification/NotificationPlaceholder.tsx +44 -43
  212. package/src/components/notification/types.ts +72 -72
  213. package/src/containers/NotificationsPage.tsx +119 -119
  214. package/src/context/anchor.tsx +37 -37
  215. package/src/context/loading.tsx +36 -36
  216. package/src/context/notification/LazyNotificationList.ts +103 -103
  217. package/src/context/notification/NotificationController.ts +104 -104
  218. package/src/context/notification/context.tsx +23 -23
  219. package/src/context/notification/hooks.ts +98 -98
  220. package/src/context/notification/types.ts +66 -66
  221. package/src/hooks/date.ts +31 -31
  222. package/src/hooks/keyboard.tsx +128 -128
  223. package/src/hooks/manual-render.tsx +10 -10
  224. package/src/hooks/service-now.tsx +233 -233
  225. package/src/hooks/text.tsx +30 -30
  226. package/src/hooks/title.tsx +28 -28
  227. package/src/hooks/use-effect-once.tsx +43 -43
  228. package/src/index.ts +19 -19
  229. package/src/notifications.ts +11 -11
  230. package/src/svg/AI.tsx +41 -41
  231. package/src/svg/CS.tsx +48 -48
  232. package/src/svg/EDP.tsx +31 -31
  233. package/src/svg/Forbidden.tsx +22 -22
  234. package/src/svg/GenericPlaceholder.tsx +20 -20
  235. package/src/svg/HUB.tsx +48 -48
  236. package/src/svg/Logo.tsx +16 -16
  237. package/src/svg/MiniLogo.tsx +12 -12
  238. package/src/svg/NotFound.tsx +16 -16
  239. package/src/svg/ServerError.tsx +33 -33
  240. package/src/svg/Unauthenticated.tsx +16 -16
  241. package/src/svg/index.ts +11 -11
  242. package/src/utils/accessibility.ts +135 -135
  243. package/src/utils/cookie.ts +73 -73
  244. package/src/utils/promise.ts +5 -5
  245. package/src/utils/read-file.ts +16 -16
  246. package/tsconfig.json +10 -10
@@ -1,265 +1,265 @@
1
- import { IconBox } from '@citric/core'
2
- import { ChevronDown, ChevronLeft, ChevronRight, ChevronUp } from '@citric/icons'
3
- import { listToClass, theme, WithStyle } from '@stack-spot/portal-theme'
4
- import { debounce } from 'lodash'
5
- import { useEffect, useRef } from 'react'
6
- import { styled } from 'styled-components'
7
-
8
- type Side = 'top' | 'right' | 'left' | 'bottom'
9
- type ScrollType = 'none' | 'wheel' | 'bar' | 'arrows'
10
-
11
- interface Props extends WithStyle {
12
- /**
13
- * How to scroll the content.
14
- * - none: overflow is hidden. No scrolling.
15
- * - wheel: the content is solely scrollable through the mouse wheel and keyboard arrows, no scroll bars are rendered.
16
- * - bar: this is the normal browser scroll. The content will be scrollable through the mouse wheel, keyboard and scrollbar.
17
- * - arrows: arrows will be placed in the far edges of the sides that must be scrolled. The content is scrolled through the mouse wheel,
18
- * keyboard and these arrows. Hovering an arrow slowly scrolls the content, clicking the arrow fully scrolls the content in its direction.
19
- * There are no scrollbars in this scenario.
20
- * @default 'none'
21
- */
22
- scroll?: ScrollType,
23
- /**
24
- * If true, a horizontal scroll can be performed by using the vertical mouse wheel.
25
- * @default false
26
- */
27
- enableHorizontalScrollWithVerticalWheel?: boolean,
28
- /**
29
- * Which sides to fade when the content overflows.
30
- *
31
- * Important: it seems the current solution doesn't support mixing vertical and horizontal scrolls. I'm not sure if this is possible, I
32
- * believed combining linear gradients would work, but it doesn't.
33
- */
34
- sides?: Side[],
35
- children?: React.ReactNode,
36
- }
37
-
38
- // Scrolling can be very fast, this sets a lower limit for us to run listeners without impacting performance.
39
- const MIN_CHECK_INTERVAL_MS = 20
40
- // The interval which to scroll the content when a scroll arrow is hovered.
41
- const SCROLL_INTERVAL_MS = 20
42
- // This sets the speed of the scroll when the user hovers a scroll arrow.
43
- const SCROLL_PX = 4
44
- // Fade masks
45
- const masks = {
46
- right: 'linear-gradient(to left, rgb(0, 0, 0, 0) 0%, rgb(0, 0, 0) min(30%, 100px), rgba(0, 0, 0) 100%)',
47
- left: 'linear-gradient(to right, rgb(0, 0, 0, 0) 0%, rgb(0, 0, 0) min(30%, 100px), rgba(0, 0, 0) 100%)',
48
- top: 'linear-gradient(to bottom, rgb(0, 0, 0, 0) 0%, rgb(0, 0, 0) min(30%, 100px), rgba(0, 0, 0) 100%)',
49
- bottom: 'linear-gradient(to top, rgb(0, 0, 0, 0) 0%, rgb(0, 0, 0) min(30%, 100px), rgba(0, 0, 0) 100%)',
50
- horizontal: 'linear-gradient(to left, rgb(0, 0, 0, 0) 0%, rgb(0, 0, 0) min(30%, 100px), rgb(0, 0, 0) max(70%, calc(100% - 100px)), rgba(0, 0, 0, 0) 100%)',
51
- vertical: 'linear-gradient(to top, rgb(0, 0, 0, 0) 0%, rgb(0, 0, 0) min(30%, 100px), rgb(0, 0, 0) max(70%, calc(100% - 100px)), rgba(0, 0, 0, 0) 100%)',
52
- }
53
-
54
- const OverflowBox = styled.div`
55
- &.hidden-scroll-bars::-webkit-scrollbar, &.scroll-arrows ::-webkit-scrollbar {
56
- width: 0;
57
- height: 0;
58
- }
59
-
60
- &.scroll-arrows {
61
- position: relative;
62
-
63
- > .content {
64
- width: 100%;
65
- }
66
- }
67
-
68
- .scroll-to-left, .scroll-to-right, .scroll-to-top, .scroll-to-bottom {
69
- position: absolute;
70
- top: 0;
71
- bottom: 0;
72
- left: 0;
73
- right: 0;
74
- width: 30px;
75
- height: 30px;
76
- display: flex;
77
- align-items: center;
78
- justify-content: center;
79
- opacity: 0;
80
- pointer-events: none;
81
- transition: opacity 0.3s;
82
-
83
- ${IconBox} {
84
- background-color: ${theme.color.light[300]};
85
- border-radius: 50%;
86
- width: 16px;
87
- height: 16px;
88
- display: flex;
89
- align-items: center;
90
- justify-content: center;
91
- }
92
-
93
- &.visible {
94
- opacity: 0.6;
95
- pointer-events: auto;
96
-
97
- &:hover {
98
- opacity: 1;
99
- }
100
- }
101
- }
102
-
103
- .scroll-to-left {
104
- right: unset;
105
- height: unset;
106
- }
107
-
108
- .scroll-to-right {
109
- left: unset;
110
- height: unset;
111
- }
112
-
113
- .scroll-to-top {
114
- bottom: unset;
115
- width: unset;
116
- }
117
-
118
- .scroll-to-bottom {
119
- top: unset;
120
- width: unset;
121
- }
122
- `
123
-
124
- /**
125
- * This component applies a fading graphical effect to its content if it overflows in any of the sides specified by the props.
126
- *
127
- * This also controls how this overflow is scrolled, introducing a new scrolling technique, which applies arrows to the overflowing side.
128
- */
129
- export const FadingOverflow = (
130
- { children, scroll = 'none', sides, enableHorizontalScrollWithVerticalWheel, className, ...props }: Props,
131
- ) => {
132
- const ref = useRef<HTMLDivElement>(null)
133
- const scrollIntervalId = useRef<number>()
134
- // Add a margin to compensate for rounding inaccuracies
135
- const SCROLL_MARGIN = 2
136
-
137
- useEffect(() => {
138
- if (!ref.current) return
139
- const element = ref.current
140
- const fadeTop = !sides || sides.includes('top')
141
- const fadeRight = !sides || sides.includes('right')
142
- const fadeBottom = !sides || sides.includes('bottom')
143
- const fadeLeft = !sides || sides.includes('left')
144
- const overflow = scroll === 'none' ? 'clip' : 'auto'
145
- element.style.overflowX = (fadeLeft || fadeRight) ? overflow : ''
146
- element.style.overflowY = (fadeTop || fadeBottom) ? overflow : ''
147
- if (scroll === 'arrows' || scroll === 'wheel') element.classList.add('hidden-scroll-bars')
148
-
149
- function stopScrolling() {
150
- clearInterval(scrollIntervalId.current)
151
- }
152
-
153
- function checkOverflow() {
154
- // masking (fading)
155
- const overflowsRight = element.clientWidth < element.scrollWidth && element.scrollLeft < (element.scrollWidth - element.clientWidth)
156
- const overflowsLeft = element.scrollLeft > 0
157
- const overflowsBottom = element.clientHeight < element.scrollHeight
158
- && element.scrollTop < (element.scrollHeight - element.clientHeight - SCROLL_MARGIN)
159
- const overflowsTop = element.scrollTop > 0
160
- const masksToApply: string[] = []
161
- if (overflowsLeft && fadeLeft && overflowsRight && fadeRight) masksToApply.push(masks.horizontal)
162
- else {
163
- if (overflowsRight && fadeRight) masksToApply.push(masks.right)
164
- if (overflowsLeft && fadeLeft) masksToApply.push(masks.left)
165
- }
166
- if (overflowsTop && fadeTop && overflowsBottom && fadeBottom) masksToApply.push(masks.vertical)
167
- else {
168
- if (overflowsTop && fadeTop) masksToApply.push(masks.top)
169
- if (overflowsBottom && fadeBottom) masksToApply.push(masks.bottom)
170
- }
171
- element.style.maskImage = masksToApply.join(', ')
172
-
173
- // arrow buttons
174
- if (scroll !== 'arrows') return
175
-
176
- function startScrolling(side: Side) {
177
- stopScrolling()
178
- const direction = side === 'bottom' || side === 'top' ? 'scrollTop' : 'scrollLeft'
179
- const multiplier = side === 'bottom' || side === 'right' ? 1 : -1
180
- scrollIntervalId.current = window.setInterval(() => {
181
- element[direction] += SCROLL_PX * multiplier
182
- }, SCROLL_INTERVAL_MS)
183
- }
184
-
185
- const startScrollingBySide = {
186
- left: () => startScrolling('left'),
187
- right: () => startScrolling('right'),
188
- top: () => startScrolling('top'),
189
- bottom: () => startScrolling('bottom'),
190
- }
191
-
192
- const scrollToMaxBySide = {
193
- left: () => element.scrollLeft = 0,
194
- right: () => element.scrollLeft = element.scrollWidth - element.clientWidth,
195
- top: () => element.scrollTop = 0,
196
- bottom: () => element.scrollTop = element.scrollHeight - element.clientHeight,
197
- }
198
-
199
- function enableArrowButton(side: Side) {
200
- const button = element.parentNode?.querySelector(`.scroll-to-${side}`)
201
- if (button?.classList.contains('visible')) return
202
- button?.classList.add('visible')
203
- button?.addEventListener('mouseenter', startScrollingBySide[side])
204
- button?.addEventListener('mouseleave', stopScrolling)
205
- button?.addEventListener('click', scrollToMaxBySide[side])
206
- }
207
-
208
- function disableArrowButton(side: Side) {
209
- const button = element.parentNode?.querySelector(`.scroll-to-${side}`)
210
- if (!button?.classList.contains('visible')) return
211
- button?.classList.remove('visible')
212
- stopScrolling()
213
- button?.removeEventListener('mouseenter', startScrollingBySide[side])
214
- button?.removeEventListener('mouseleave', stopScrolling)
215
- button?.removeEventListener('click', scrollToMaxBySide[side])
216
- }
217
-
218
- if (overflowsRight && fadeRight) enableArrowButton('right')
219
- else disableArrowButton('right')
220
- if (overflowsLeft && fadeLeft) enableArrowButton('left')
221
- else disableArrowButton('left')
222
- if (overflowsTop && fadeTop) enableArrowButton('top')
223
- else disableArrowButton('top')
224
- if (overflowsBottom && fadeBottom) enableArrowButton('bottom')
225
- else disableArrowButton('bottom')
226
- }
227
-
228
- const debouncedCheck = debounce(checkOverflow, MIN_CHECK_INTERVAL_MS)
229
- const resizeObserver = new ResizeObserver(debouncedCheck)
230
- resizeObserver.observe(element)
231
- element.addEventListener('scroll', debouncedCheck)
232
-
233
- return () => {
234
- stopScrolling()
235
- resizeObserver.disconnect()
236
- element.removeEventListener('scroll', debouncedCheck)
237
- }
238
- }, [sides, ref.current, scroll])
239
-
240
- useEffect(() => {
241
- if (!enableHorizontalScrollWithVerticalWheel || !ref.current) return
242
- const element = ref.current
243
-
244
- function scrollWithWheel(event: WheelEvent) {
245
- if (event.deltaY) {
246
- element.scrollLeft += event.deltaY
247
- event.preventDefault()
248
- event.stopPropagation()
249
- }
250
- }
251
-
252
- element.addEventListener('wheel', scrollWithWheel)
253
- return () => element.removeEventListener('wheel', scrollWithWheel)
254
- }, [ref.current, enableHorizontalScrollWithVerticalWheel])
255
-
256
- return scroll === 'arrows' ? (
257
- <OverflowBox {...props} className={listToClass(['scroll-arrows', className])}>
258
- <div className="content" ref={ref}>{children}</div>
259
- <div className="scroll-to-left" aria-hidden><IconBox size="xs"><ChevronLeft /></IconBox></div>
260
- <div className="scroll-to-right" aria-hidden><IconBox size="xs"><ChevronRight /></IconBox></div>
261
- <div className="scroll-to-top" aria-hidden><IconBox size="xs"><ChevronUp /></IconBox></div>
262
- <div className="scroll-to-bottom" aria-hidden><IconBox size="xs"><ChevronDown /></IconBox></div>
263
- </OverflowBox>
264
- ) : <OverflowBox {...props} className={className} ref={ref}>{children}</OverflowBox>
265
- }
1
+ import { IconBox } from '@citric/core'
2
+ import { ChevronDown, ChevronLeft, ChevronRight, ChevronUp } from '@citric/icons'
3
+ import { listToClass, theme, WithStyle } from '@stack-spot/portal-theme'
4
+ import { debounce } from 'lodash'
5
+ import { useEffect, useRef } from 'react'
6
+ import { styled } from 'styled-components'
7
+
8
+ type Side = 'top' | 'right' | 'left' | 'bottom'
9
+ type ScrollType = 'none' | 'wheel' | 'bar' | 'arrows'
10
+
11
+ interface Props extends WithStyle {
12
+ /**
13
+ * How to scroll the content.
14
+ * - none: overflow is hidden. No scrolling.
15
+ * - wheel: the content is solely scrollable through the mouse wheel and keyboard arrows, no scroll bars are rendered.
16
+ * - bar: this is the normal browser scroll. The content will be scrollable through the mouse wheel, keyboard and scrollbar.
17
+ * - arrows: arrows will be placed in the far edges of the sides that must be scrolled. The content is scrolled through the mouse wheel,
18
+ * keyboard and these arrows. Hovering an arrow slowly scrolls the content, clicking the arrow fully scrolls the content in its direction.
19
+ * There are no scrollbars in this scenario.
20
+ * @default 'none'
21
+ */
22
+ scroll?: ScrollType,
23
+ /**
24
+ * If true, a horizontal scroll can be performed by using the vertical mouse wheel.
25
+ * @default false
26
+ */
27
+ enableHorizontalScrollWithVerticalWheel?: boolean,
28
+ /**
29
+ * Which sides to fade when the content overflows.
30
+ *
31
+ * Important: it seems the current solution doesn't support mixing vertical and horizontal scrolls. I'm not sure if this is possible, I
32
+ * believed combining linear gradients would work, but it doesn't.
33
+ */
34
+ sides?: Side[],
35
+ children?: React.ReactNode,
36
+ }
37
+
38
+ // Scrolling can be very fast, this sets a lower limit for us to run listeners without impacting performance.
39
+ const MIN_CHECK_INTERVAL_MS = 20
40
+ // The interval which to scroll the content when a scroll arrow is hovered.
41
+ const SCROLL_INTERVAL_MS = 20
42
+ // This sets the speed of the scroll when the user hovers a scroll arrow.
43
+ const SCROLL_PX = 4
44
+ // Fade masks
45
+ const masks = {
46
+ right: 'linear-gradient(to left, rgb(0, 0, 0, 0) 0%, rgb(0, 0, 0) min(30%, 100px), rgba(0, 0, 0) 100%)',
47
+ left: 'linear-gradient(to right, rgb(0, 0, 0, 0) 0%, rgb(0, 0, 0) min(30%, 100px), rgba(0, 0, 0) 100%)',
48
+ top: 'linear-gradient(to bottom, rgb(0, 0, 0, 0) 0%, rgb(0, 0, 0) min(30%, 100px), rgba(0, 0, 0) 100%)',
49
+ bottom: 'linear-gradient(to top, rgb(0, 0, 0, 0) 0%, rgb(0, 0, 0) min(30%, 100px), rgba(0, 0, 0) 100%)',
50
+ horizontal: 'linear-gradient(to left, rgb(0, 0, 0, 0) 0%, rgb(0, 0, 0) min(30%, 100px), rgb(0, 0, 0) max(70%, calc(100% - 100px)), rgba(0, 0, 0, 0) 100%)',
51
+ vertical: 'linear-gradient(to top, rgb(0, 0, 0, 0) 0%, rgb(0, 0, 0) min(30%, 100px), rgb(0, 0, 0) max(70%, calc(100% - 100px)), rgba(0, 0, 0, 0) 100%)',
52
+ }
53
+
54
+ const OverflowBox = styled.div`
55
+ &.hidden-scroll-bars::-webkit-scrollbar, &.scroll-arrows ::-webkit-scrollbar {
56
+ width: 0;
57
+ height: 0;
58
+ }
59
+
60
+ &.scroll-arrows {
61
+ position: relative;
62
+
63
+ > .content {
64
+ width: 100%;
65
+ }
66
+ }
67
+
68
+ .scroll-to-left, .scroll-to-right, .scroll-to-top, .scroll-to-bottom {
69
+ position: absolute;
70
+ top: 0;
71
+ bottom: 0;
72
+ left: 0;
73
+ right: 0;
74
+ width: 30px;
75
+ height: 30px;
76
+ display: flex;
77
+ align-items: center;
78
+ justify-content: center;
79
+ opacity: 0;
80
+ pointer-events: none;
81
+ transition: opacity 0.3s;
82
+
83
+ ${IconBox} {
84
+ background-color: ${theme.color.light[300]};
85
+ border-radius: 50%;
86
+ width: 16px;
87
+ height: 16px;
88
+ display: flex;
89
+ align-items: center;
90
+ justify-content: center;
91
+ }
92
+
93
+ &.visible {
94
+ opacity: 0.6;
95
+ pointer-events: auto;
96
+
97
+ &:hover {
98
+ opacity: 1;
99
+ }
100
+ }
101
+ }
102
+
103
+ .scroll-to-left {
104
+ right: unset;
105
+ height: unset;
106
+ }
107
+
108
+ .scroll-to-right {
109
+ left: unset;
110
+ height: unset;
111
+ }
112
+
113
+ .scroll-to-top {
114
+ bottom: unset;
115
+ width: unset;
116
+ }
117
+
118
+ .scroll-to-bottom {
119
+ top: unset;
120
+ width: unset;
121
+ }
122
+ `
123
+
124
+ /**
125
+ * This component applies a fading graphical effect to its content if it overflows in any of the sides specified by the props.
126
+ *
127
+ * This also controls how this overflow is scrolled, introducing a new scrolling technique, which applies arrows to the overflowing side.
128
+ */
129
+ export const FadingOverflow = (
130
+ { children, scroll = 'none', sides, enableHorizontalScrollWithVerticalWheel, className, ...props }: Props,
131
+ ) => {
132
+ const ref = useRef<HTMLDivElement>(null)
133
+ const scrollIntervalId = useRef<number>()
134
+ // Add a margin to compensate for rounding inaccuracies
135
+ const SCROLL_MARGIN = 2
136
+
137
+ useEffect(() => {
138
+ if (!ref.current) return
139
+ const element = ref.current
140
+ const fadeTop = !sides || sides.includes('top')
141
+ const fadeRight = !sides || sides.includes('right')
142
+ const fadeBottom = !sides || sides.includes('bottom')
143
+ const fadeLeft = !sides || sides.includes('left')
144
+ const overflow = scroll === 'none' ? 'clip' : 'auto'
145
+ element.style.overflowX = (fadeLeft || fadeRight) ? overflow : ''
146
+ element.style.overflowY = (fadeTop || fadeBottom) ? overflow : ''
147
+ if (scroll === 'arrows' || scroll === 'wheel') element.classList.add('hidden-scroll-bars')
148
+
149
+ function stopScrolling() {
150
+ clearInterval(scrollIntervalId.current)
151
+ }
152
+
153
+ function checkOverflow() {
154
+ // masking (fading)
155
+ const overflowsRight = element.clientWidth < element.scrollWidth && element.scrollLeft < (element.scrollWidth - element.clientWidth)
156
+ const overflowsLeft = element.scrollLeft > 0
157
+ const overflowsBottom = element.clientHeight < element.scrollHeight
158
+ && element.scrollTop < (element.scrollHeight - element.clientHeight - SCROLL_MARGIN)
159
+ const overflowsTop = element.scrollTop > 0
160
+ const masksToApply: string[] = []
161
+ if (overflowsLeft && fadeLeft && overflowsRight && fadeRight) masksToApply.push(masks.horizontal)
162
+ else {
163
+ if (overflowsRight && fadeRight) masksToApply.push(masks.right)
164
+ if (overflowsLeft && fadeLeft) masksToApply.push(masks.left)
165
+ }
166
+ if (overflowsTop && fadeTop && overflowsBottom && fadeBottom) masksToApply.push(masks.vertical)
167
+ else {
168
+ if (overflowsTop && fadeTop) masksToApply.push(masks.top)
169
+ if (overflowsBottom && fadeBottom) masksToApply.push(masks.bottom)
170
+ }
171
+ element.style.maskImage = masksToApply.join(', ')
172
+
173
+ // arrow buttons
174
+ if (scroll !== 'arrows') return
175
+
176
+ function startScrolling(side: Side) {
177
+ stopScrolling()
178
+ const direction = side === 'bottom' || side === 'top' ? 'scrollTop' : 'scrollLeft'
179
+ const multiplier = side === 'bottom' || side === 'right' ? 1 : -1
180
+ scrollIntervalId.current = window.setInterval(() => {
181
+ element[direction] += SCROLL_PX * multiplier
182
+ }, SCROLL_INTERVAL_MS)
183
+ }
184
+
185
+ const startScrollingBySide = {
186
+ left: () => startScrolling('left'),
187
+ right: () => startScrolling('right'),
188
+ top: () => startScrolling('top'),
189
+ bottom: () => startScrolling('bottom'),
190
+ }
191
+
192
+ const scrollToMaxBySide = {
193
+ left: () => element.scrollLeft = 0,
194
+ right: () => element.scrollLeft = element.scrollWidth - element.clientWidth,
195
+ top: () => element.scrollTop = 0,
196
+ bottom: () => element.scrollTop = element.scrollHeight - element.clientHeight,
197
+ }
198
+
199
+ function enableArrowButton(side: Side) {
200
+ const button = element.parentNode?.querySelector(`.scroll-to-${side}`)
201
+ if (button?.classList.contains('visible')) return
202
+ button?.classList.add('visible')
203
+ button?.addEventListener('mouseenter', startScrollingBySide[side])
204
+ button?.addEventListener('mouseleave', stopScrolling)
205
+ button?.addEventListener('click', scrollToMaxBySide[side])
206
+ }
207
+
208
+ function disableArrowButton(side: Side) {
209
+ const button = element.parentNode?.querySelector(`.scroll-to-${side}`)
210
+ if (!button?.classList.contains('visible')) return
211
+ button?.classList.remove('visible')
212
+ stopScrolling()
213
+ button?.removeEventListener('mouseenter', startScrollingBySide[side])
214
+ button?.removeEventListener('mouseleave', stopScrolling)
215
+ button?.removeEventListener('click', scrollToMaxBySide[side])
216
+ }
217
+
218
+ if (overflowsRight && fadeRight) enableArrowButton('right')
219
+ else disableArrowButton('right')
220
+ if (overflowsLeft && fadeLeft) enableArrowButton('left')
221
+ else disableArrowButton('left')
222
+ if (overflowsTop && fadeTop) enableArrowButton('top')
223
+ else disableArrowButton('top')
224
+ if (overflowsBottom && fadeBottom) enableArrowButton('bottom')
225
+ else disableArrowButton('bottom')
226
+ }
227
+
228
+ const debouncedCheck = debounce(checkOverflow, MIN_CHECK_INTERVAL_MS)
229
+ const resizeObserver = new ResizeObserver(debouncedCheck)
230
+ resizeObserver.observe(element)
231
+ element.addEventListener('scroll', debouncedCheck)
232
+
233
+ return () => {
234
+ stopScrolling()
235
+ resizeObserver.disconnect()
236
+ element.removeEventListener('scroll', debouncedCheck)
237
+ }
238
+ }, [sides, ref.current, scroll])
239
+
240
+ useEffect(() => {
241
+ if (!enableHorizontalScrollWithVerticalWheel || !ref.current) return
242
+ const element = ref.current
243
+
244
+ function scrollWithWheel(event: WheelEvent) {
245
+ if (event.deltaY) {
246
+ element.scrollLeft += event.deltaY
247
+ event.preventDefault()
248
+ event.stopPropagation()
249
+ }
250
+ }
251
+
252
+ element.addEventListener('wheel', scrollWithWheel)
253
+ return () => element.removeEventListener('wheel', scrollWithWheel)
254
+ }, [ref.current, enableHorizontalScrollWithVerticalWheel])
255
+
256
+ return scroll === 'arrows' ? (
257
+ <OverflowBox {...props} className={listToClass(['scroll-arrows', className])}>
258
+ <div className="content" ref={ref}>{children}</div>
259
+ <div className="scroll-to-left" aria-hidden><IconBox size="xs"><ChevronLeft /></IconBox></div>
260
+ <div className="scroll-to-right" aria-hidden><IconBox size="xs"><ChevronRight /></IconBox></div>
261
+ <div className="scroll-to-top" aria-hidden><IconBox size="xs"><ChevronUp /></IconBox></div>
262
+ <div className="scroll-to-bottom" aria-hidden><IconBox size="xs"><ChevronDown /></IconBox></div>
263
+ </OverflowBox>
264
+ ) : <OverflowBox {...props} className={className} ref={ref}>{children}</OverflowBox>
265
+ }