@stack-spot/portal-components 1.2.0 → 1.3.1-rc

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 (77) hide show
  1. package/dist/components/AsyncContent.d.ts +39 -0
  2. package/dist/components/AsyncContent.d.ts.map +1 -0
  3. package/dist/components/AsyncContent.js +27 -0
  4. package/dist/components/AsyncContent.js.map +1 -0
  5. package/dist/components/InfiniteScroll.d.ts +3 -0
  6. package/dist/components/InfiniteScroll.d.ts.map +1 -0
  7. package/dist/components/InfiniteScroll.js +5 -0
  8. package/dist/components/InfiniteScroll.js.map +1 -0
  9. package/dist/components/LazyMarkdown/BlockquoteMd.d.ts +4 -0
  10. package/dist/components/LazyMarkdown/BlockquoteMd.d.ts.map +1 -0
  11. package/dist/components/LazyMarkdown/BlockquoteMd.js +64 -0
  12. package/dist/components/LazyMarkdown/BlockquoteMd.js.map +1 -0
  13. package/dist/components/LazyMarkdown/CodeViewer.d.ts +19 -0
  14. package/dist/components/LazyMarkdown/CodeViewer.d.ts.map +1 -0
  15. package/dist/components/LazyMarkdown/CodeViewer.js +116 -0
  16. package/dist/components/LazyMarkdown/CodeViewer.js.map +1 -0
  17. package/dist/components/LazyMarkdown/Markdown.d.ts +6 -0
  18. package/dist/components/LazyMarkdown/Markdown.d.ts.map +1 -0
  19. package/dist/components/LazyMarkdown/Markdown.js +87 -0
  20. package/dist/components/LazyMarkdown/Markdown.js.map +1 -0
  21. package/dist/components/LazyMarkdown/MarkdownButton.d.ts +8 -0
  22. package/dist/components/LazyMarkdown/MarkdownButton.d.ts.map +1 -0
  23. package/dist/components/LazyMarkdown/MarkdownButton.js +4 -0
  24. package/dist/components/LazyMarkdown/MarkdownButton.js.map +1 -0
  25. package/dist/components/LazyMarkdown/Video.d.ts +4 -0
  26. package/dist/components/LazyMarkdown/Video.d.ts.map +1 -0
  27. package/dist/components/LazyMarkdown/Video.js +3 -0
  28. package/dist/components/LazyMarkdown/Video.js.map +1 -0
  29. package/dist/components/LazyMarkdown/index.d.ts +8 -0
  30. package/dist/components/LazyMarkdown/index.d.ts.map +1 -0
  31. package/dist/components/LazyMarkdown/index.js +8 -0
  32. package/dist/components/LazyMarkdown/index.js.map +1 -0
  33. package/dist/components/Notifications/NotificationComponent.d.ts +24 -0
  34. package/dist/components/Notifications/NotificationComponent.d.ts.map +1 -0
  35. package/dist/components/Notifications/NotificationComponent.js +114 -0
  36. package/dist/components/Notifications/NotificationComponent.js.map +1 -0
  37. package/dist/components/Notifications/NotificationItem.d.ts +11 -0
  38. package/dist/components/Notifications/NotificationItem.d.ts.map +1 -0
  39. package/dist/components/Notifications/NotificationItem.js +59 -0
  40. package/dist/components/Notifications/NotificationItem.js.map +1 -0
  41. package/dist/components/Notifications/index.d.ts +4 -0
  42. package/dist/components/Notifications/index.d.ts.map +1 -0
  43. package/dist/components/Notifications/index.js +4 -0
  44. package/dist/components/Notifications/index.js.map +1 -0
  45. package/dist/components/Notifications/types.d.ts +38 -0
  46. package/dist/components/Notifications/types.d.ts.map +1 -0
  47. package/dist/components/Notifications/types.js +20 -0
  48. package/dist/components/Notifications/types.js.map +1 -0
  49. package/dist/components/ScrollView.d.ts +27 -0
  50. package/dist/components/ScrollView.d.ts.map +1 -0
  51. package/dist/components/ScrollView.js +25 -0
  52. package/dist/components/ScrollView.js.map +1 -0
  53. package/dist/components/StatusCircle.d.ts +14 -0
  54. package/dist/components/StatusCircle.d.ts.map +1 -0
  55. package/dist/components/StatusCircle.js +26 -0
  56. package/dist/components/StatusCircle.js.map +1 -0
  57. package/dist/hooks/date.d.ts +22 -0
  58. package/dist/hooks/date.d.ts.map +1 -0
  59. package/dist/hooks/date.js +40 -0
  60. package/dist/hooks/date.js.map +1 -0
  61. package/package.json +16 -5
  62. package/src/components/AsyncContent.tsx +70 -0
  63. package/src/components/InfiniteScroll.tsx +13 -0
  64. package/src/components/LazyMarkdown/BlockquoteMd.tsx +94 -0
  65. package/src/components/LazyMarkdown/CodeViewer.tsx +154 -0
  66. package/src/components/LazyMarkdown/Markdown.tsx +107 -0
  67. package/src/components/LazyMarkdown/MarkdownButton.tsx +13 -0
  68. package/src/components/LazyMarkdown/Video.tsx +6 -0
  69. package/src/components/LazyMarkdown/index.tsx +18 -0
  70. package/src/components/Notifications/NotificationComponent.tsx +264 -0
  71. package/src/components/Notifications/NotificationItem.tsx +136 -0
  72. package/src/components/Notifications/index.tsx +3 -0
  73. package/src/components/Notifications/types.ts +46 -0
  74. package/src/components/ScrollView.tsx +51 -0
  75. package/src/components/StatusCircle.tsx +44 -0
  76. package/src/hooks/date.ts +43 -0
  77. package/src/index.ts +0 -1
@@ -0,0 +1,94 @@
1
+ import { Blockquote, Box, Flex, IconBox, Text } from '@citric/core'
2
+ import { OneOfColorSchemes } from '@citric/core/dist/theme-types'
3
+ import * as icons from '@citric/icons'
4
+ import { Dictionary, useTranslate } from '@stack-spot/portal-translate'
5
+ import { createElement } from 'react'
6
+
7
+ type IconName = keyof typeof icons
8
+
9
+ export const BlockquoteMd = ({ props }: { props: any }) => {
10
+ const t = useTranslate(dictionary)
11
+ const arrayNode = props.node
12
+ const blockItem = arrayNode?.children[1]?.children[0]?.value
13
+ const blockStyle = blockquoteAlertStyle(blockItem)
14
+
15
+ if (blockStyle) {
16
+ const childrenBlock = arrayNode?.children[3]?.children[1].children[0].value
17
+ return (
18
+ <Flex sx={{ borderRadius: 'sm', bg: 'light.500', my: 3 }}>
19
+ <Blockquote colorScheme={blockStyle.color}>
20
+ <Box>
21
+ <Flex>
22
+ <IconBox appearance="square" colorIcon={`${blockStyle.color}.400`} sx={{ mr: 4 }}>
23
+ {createElement(icons[blockStyle.icon])}
24
+ </IconBox>
25
+ <Text sx={{ color: `${blockStyle.color}.400` }} weight="medium">
26
+ {t[blockItem as keyof typeof t]}
27
+ </Text>
28
+ </Flex>
29
+ <Box my={3}>
30
+ <Text>{childrenBlock}</Text>
31
+ </Box>
32
+ </Box>
33
+ </Blockquote>
34
+ </Flex>
35
+ )
36
+ }
37
+
38
+ return (
39
+ <Flex sx={{ borderRadius: 'sm', bg: 'light.500', my: 3 }}>
40
+ <Blockquote>
41
+ <Text {...props} />
42
+ </Blockquote>
43
+ </Flex>
44
+ )
45
+ }
46
+
47
+ function blockquoteAlertStyle(blockRule: string) {
48
+ const style: Record<string, { color: OneOfColorSchemes, icon: IconName }> = {
49
+ '[!NOTE]': {
50
+ color: 'inverse',
51
+ icon: 'Document',
52
+ },
53
+ '[!INFO]': {
54
+ color: 'secondary',
55
+ icon: 'InfoCircle',
56
+ },
57
+ '[!IMPORTANT]': {
58
+ color: 'tertiary',
59
+ icon: 'MobileComments',
60
+ },
61
+ '[!TIP]': {
62
+ color: 'success',
63
+ icon: 'Star',
64
+ },
65
+ '[!WARNING]': {
66
+ color: 'warning',
67
+ icon: 'ExclamationTriangle',
68
+ },
69
+ '[!DANGER]': {
70
+ color: 'danger',
71
+ icon: 'Bug',
72
+ },
73
+ }
74
+ return style[blockRule]
75
+ }
76
+
77
+ const dictionary = {
78
+ en: {
79
+ '[!NOTE]': 'Note',
80
+ '[!INFO]': 'Info',
81
+ '[!IMPORTANT]': 'Important',
82
+ '[!TIP]': 'Tip',
83
+ '[!WARNING]': 'Warning',
84
+ '[!DANGER]': 'Danger',
85
+ },
86
+ pt: {
87
+ '[!NOTE]': 'Nota',
88
+ '[!INFO]': 'Informação',
89
+ '[!IMPORTANT]': 'Importante',
90
+ '[!TIP]': 'Dica',
91
+ '[!WARNING]': 'Atenção',
92
+ '[!DANGER]': 'Risco',
93
+ },
94
+ } satisfies Dictionary
@@ -0,0 +1,154 @@
1
+ import { Box, Flex, Text } from '@citric/core'
2
+ import { getColor, getRadius } from '@citric/core/dist/utils/theme'
3
+ import { Copy, Refresh } from '@citric/icons'
4
+ import { IconButton } from '@citric/ui'
5
+ import { useTranslate } from '@stack-spot/portal-translate'
6
+ import SyntaxHighlighter from 'react-syntax-highlighter'
7
+ import styled from 'styled-components'
8
+
9
+ const style = {
10
+ scrollable: {
11
+ minHeight: '64px',
12
+ maxHeight: '232px',
13
+ overflow: 'auto',
14
+ '&::-webkit-scrollbar': {
15
+ width: '4px',
16
+ height: '4px',
17
+ },
18
+ '&::-webkit-scrollbar-track': {
19
+ background: 'light.400',
20
+ borderRadius: '2px',
21
+ },
22
+ '&::-webkit-scrollbar-thumb': {
23
+ background: 'light.700',
24
+ borderRadius: '2px',
25
+ },
26
+ },
27
+ }
28
+
29
+ const Wrapper = styled(Box)`
30
+ background-color: ${({ theme }) => getColor(theme as any, 'gray.900')};
31
+ border-radius: ${({ theme }) => getRadius(theme as any, 'xs')};
32
+ position: relative;
33
+ padding: 8px;
34
+ overflow: hidden;
35
+
36
+ > .scrollable-container {
37
+ min-height: 64px;
38
+ max-height: 232px;
39
+ overflow: auto;
40
+ &::-webkit-scrollbar {
41
+ width: 4px;
42
+ height: 4px;
43
+ }
44
+ &::-webkit-scrollbar-track {
45
+ background: ${({ theme }) => getColor(theme as any, 'light.400')};
46
+ border-radius: 2px;
47
+ }
48
+ &::-webkit-scrollbar-thumb,
49
+ &::-webkit-scrollbar-thumb:hover {
50
+ background: ${({ theme }) => getColor(theme as any, 'light.700')};
51
+ border-radius: 2px;
52
+ }
53
+ }
54
+
55
+ pre {
56
+ background-color: transparent;
57
+ font-family: 'Roboto Mono';
58
+ font-size: 14.4px;
59
+ padding: 0;
60
+ margin-top: 0;
61
+ overflow: unset;
62
+ text-shadow: none;
63
+ color: #fff;
64
+ }
65
+
66
+ .hljs-string {
67
+ color: ${({ theme }) => getColor(theme as any, 'teal.300')};
68
+ }
69
+ .hljs-tag {
70
+ color: ${({ theme }) => getColor(theme as any, 'teal')};
71
+ }
72
+ .hljs-attr {
73
+ color: #fff;
74
+ }
75
+ .hljs-number {
76
+ color: #fff;
77
+ }
78
+ .hljs-property {
79
+ color: #fff;
80
+ }
81
+ .hljs-keyword {
82
+ color: #75dcff;
83
+ }
84
+ .comment {
85
+ color: #8e8e93;
86
+ }
87
+ .hljs-expression {
88
+ color: #f57f17;
89
+ }
90
+
91
+ .button-group {
92
+ position: absolute;
93
+ top: 8px;
94
+ right: 24px;
95
+ display: flex;
96
+
97
+ button {
98
+ margin-left: 6px;
99
+ &:focus {
100
+ background-color: ${({ theme }) => getColor(theme as any, 'light.300')};
101
+ }
102
+ }
103
+ }
104
+ `
105
+
106
+ interface Props {
107
+ language: string,
108
+ data: string,
109
+ onClickRefresh?: () => void,
110
+ height?: string,
111
+ scrollable?: boolean,
112
+ copyButton?: boolean,
113
+ }
114
+ export const CodeViewer = ({ language, data, onClickRefresh, scrollable = true, copyButton }: Props) => {
115
+ const t = useTranslate(codeViewerLocale)
116
+
117
+ return (
118
+ <Wrapper>
119
+ {data ? (
120
+ <>
121
+ <Flex style={scrollable ? style.scrollable : undefined}>
122
+ <SyntaxHighlighter language={language} showLineNumbers useInlineStyles={false}>
123
+ {String(data).replace(/\n$/, '')}
124
+ </SyntaxHighlighter>
125
+ </Flex>
126
+ <div className="button-group">
127
+ {onClickRefresh && (
128
+ <IconButton onClick={onClickRefresh}>
129
+ <Refresh />
130
+ </IconButton>
131
+ )}
132
+
133
+ {copyButton ? (
134
+ <IconButton onClick={() => navigator.clipboard.writeText(data)}>
135
+ <Copy />
136
+ </IconButton>
137
+ ) : null}
138
+ </div>
139
+ </>
140
+ ) : (
141
+ <Text appearance="body2">{t.noData}</Text>
142
+ )}
143
+ </Wrapper>
144
+ )
145
+ }
146
+
147
+ export const codeViewerLocale = {
148
+ en: {
149
+ noData: 'No data available.',
150
+ },
151
+ pt: {
152
+ noData: 'Sem dados disponíveis.',
153
+ },
154
+ }
@@ -0,0 +1,107 @@
1
+ import { Box, Image, Link, Text } from '@citric/core'
2
+ import { SxProp } from '@citric/core/dist/component-style'
3
+ import { Table, Tbody, Td, Th, Thead, Tr } from '@citric/ui'
4
+ import ReactMarkdown from 'react-markdown'
5
+ import rehypeRaw from 'rehype-raw'
6
+ import remarkGfm from 'remark-gfm'
7
+ import { BlockquoteMd } from './BlockquoteMd'
8
+ import { CodeViewer } from './CodeViewer'
9
+ import { Video } from './Video'
10
+
11
+ interface MarkdownProps {
12
+ children: string,
13
+ }
14
+
15
+ const styles: Record<string, SxProp | Record<string, SxProp>> = {
16
+ title: {
17
+ mb: 3,
18
+ },
19
+ boxContainer: {
20
+ position: 'relative',
21
+ bg: 'light.300',
22
+ borderRadius: 'sm',
23
+ mb: 10,
24
+ },
25
+ table: {
26
+ borderRadius: 'xs',
27
+ },
28
+
29
+ customScroll: {
30
+ height: '232px',
31
+ overflow: 'auto',
32
+ '&::-webkit-scrollbar': {
33
+ width: '2px',
34
+ },
35
+ '&::-webkit-scrollbar-track': {
36
+ background: 'light.400',
37
+ },
38
+ '&::-webkit-scrollbar-thumb': {
39
+ background: 'primary.500',
40
+ },
41
+ '&::-webkit-scrollbar-thumb:hover': {
42
+ background: 'primary.500',
43
+ },
44
+ },
45
+ refreshButton: {
46
+ position: 'absolute',
47
+ top: '8px',
48
+ right: '48px',
49
+ },
50
+ copyButton: {
51
+ position: 'absolute',
52
+ top: '8px',
53
+ right: '16px',
54
+ },
55
+ code: {
56
+ bg: 'blue.900',
57
+ color: 'blue.100',
58
+ px: 1,
59
+ py: 1,
60
+ borderRadius: 'xs',
61
+ },
62
+ }
63
+
64
+ const component = () => ({
65
+ p: (props: any) => <Text appearance="body2" {...props} />,
66
+ strong: (props: any) => <Text weight="bold" colorScheme="light.700" as="strong" {...props} />,
67
+ h1: (props: any) => <Text as="h1" appearance="h2" gutterBottom {...props} sx={styles.title} />,
68
+ h2: (props: any) => <Text as="h2" appearance="h3" gutterBottom {...props} sx={styles.title} />,
69
+ h3: (props: any) => <Text as="h3" appearance="h4" gutterBottom {...props} sx={styles.title} />,
70
+ h4: (props: any) => <Text as="h4" appearance="h5" gutterBottom {...props} sx={styles.title} />,
71
+ a: (props: any) => <Link colorScheme="primary" {...props} target="_blank" />,
72
+ table: (props: any) => <Table appearance="striped" {...props} sx={styles.table} />,
73
+ thead: (props: any) => <Thead {...props} />,
74
+ tbody: (props: any) => <Tbody {...props} />,
75
+ tr: (props: any) => <Tr {...props} />,
76
+ td: (props: any) => <Td {...props} />,
77
+ th: (props: any) => <Th {...props} />,
78
+ li: (props: any) => <Text as="li" appearance="body2" colorScheme="light.700" {...props} />,
79
+ br: (props: any) => <Box my={5} {...props} />,
80
+ blockquote: (props: any) => <BlockquoteMd {...{ props }} />,
81
+ video: (props: any) => <Video {...props} />,
82
+ img: (props: any) => <Image {...props} />,
83
+ pre(props: any) {
84
+ const propComp = props.children.props
85
+ const match = /language-(\w+)/.exec(propComp.className || '')
86
+ const inferredLanguage = match && match.length > 1 ? match[1] : undefined
87
+
88
+ return (
89
+ <Box sx={{ ...styles.boxContainer }}>
90
+ <CodeViewer language={inferredLanguage} {...propComp} data={propComp.children} />
91
+ </Box>
92
+ )
93
+ },
94
+ code: ({ ...props }: any) =>
95
+ <Text as="code" sx={styles.code} {...props}>
96
+ {props.children}
97
+ </Text>,
98
+ })
99
+
100
+ const Markdown = ({ children }: MarkdownProps) => (
101
+ <ReactMarkdown rehypePlugins={[rehypeRaw]} remarkPlugins={[remarkGfm]} components={component()}>
102
+ {children}
103
+ </ReactMarkdown>
104
+ )
105
+
106
+ // eslint-disable-next-line import/no-default-export
107
+ export default Markdown
@@ -0,0 +1,13 @@
1
+ import { IconButton } from '@citric/ui'
2
+ import { ReactNode } from 'react'
3
+
4
+ interface MarkdownButtonProps {
5
+ onClick?: () => void,
6
+ children: ReactNode,
7
+ }
8
+
9
+ export const MarkdownButton = ({ ...props }: MarkdownButtonProps) => (
10
+ <IconButton sx={{ position: 'absolute', top: '16px', right: '16px' }} onClick={props.onClick}>
11
+ {props.children}
12
+ </IconButton>
13
+ )
@@ -0,0 +1,6 @@
1
+ export const Video = ({ children }: { children: string }) => (
2
+ <iframe
3
+ src={children}
4
+ allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
5
+ />
6
+ )
@@ -0,0 +1,18 @@
1
+ import { Skeleton } from '@citric/ui'
2
+ import { Suspense, lazy } from 'react'
3
+ import { MarkdownButton } from './MarkdownButton'
4
+
5
+ const Markdown = lazy(() => import('./Markdown'))
6
+
7
+ interface Props {
8
+ children: string,
9
+ language?: string,
10
+ }
11
+
12
+ const LazyMarkdown = ({ children }: Props) => (
13
+ <Suspense fallback={<Skeleton />}>
14
+ <Markdown>{children}</Markdown>
15
+ </Suspense>
16
+ )
17
+
18
+ export { LazyMarkdown, MarkdownButton }
@@ -0,0 +1,264 @@
1
+ import { Box, Button, Flex, IconBox, Text } from '@citric/core'
2
+ import { Bell, TimesMini } from '@citric/icons'
3
+ import { listToClass, theme } from '@stack-spot/portal-theme'
4
+ import { Dictionary, useTranslate } from '@stack-spot/portal-translate'
5
+ import { ReactElement, useState } from 'react'
6
+ import styled from 'styled-components'
7
+ import { AsyncContent, ErrorProps } from '../AsyncContent'
8
+ import { InfiniteScroll } from '../InfiniteScroll'
9
+ import { ScrollView } from '../ScrollView'
10
+ import { StatusCircle } from '../StatusCircle'
11
+ import { NotificationItem } from './NotificationItem'
12
+ import { NotificationType, NotificationTypeFilters, StackspotNotification, UnreadType } from './types'
13
+
14
+ interface Props {
15
+ hasUnreadNotification?: boolean,
16
+ }
17
+
18
+ const ANIMATION_DURATION_MS = 300
19
+ const MAX_HEIGHT_TRANSITION = `max-height ease-in ${ANIMATION_DURATION_MS / 1000}s`
20
+
21
+ const NotificationsComponent = styled(Flex) <{ $scroll?: boolean }>`
22
+ max-height: 0;
23
+ z-index: 1;
24
+ visibility: hidden;
25
+ position: absolute;
26
+ top: calc(var(--header-height) - 2px);
27
+ right: -270%;
28
+ width: 400px;
29
+
30
+ &.visible {
31
+ min-height: 802px;
32
+ visibility: visible;
33
+ transition: ${MAX_HEIGHT_TRANSITION};
34
+ }
35
+
36
+ .content {
37
+ border-radius: 4px;
38
+ border: 1px solid ${theme.color.light[400]};
39
+ box-shadow: 4px 4px 48px ${theme.color.danger.contrastText};
40
+ background-color: ${theme.color.light[300]};
41
+ overflow-y: ${({ $scroll }) => $scroll ? 'auto' : 'hidden'};
42
+ overflow-x: hidden;
43
+ transition: ${MAX_HEIGHT_TRANSITION}, visibility 0s ${ANIMATION_DURATION_MS / 1000}s;
44
+ }
45
+
46
+ &::after {
47
+ content: '';
48
+ position: absolute;
49
+ border-width: 10px;
50
+ border-style: solid;
51
+ border-color: transparent;
52
+ bottom: 100%;
53
+ left: 74%;
54
+ margin-left: -5px;
55
+ transform: rotate(180deg);
56
+ border-top-color: ${theme.color.light[400]};
57
+ }
58
+ `
59
+
60
+ const StyledBox = styled(Box)`
61
+ width: 100%;
62
+ > div:first-child{
63
+ width: 100%;
64
+ }
65
+ `
66
+
67
+ interface NotificationsFilterProps {
68
+ type?: NotificationTypeFilters,
69
+ onChangeFilterType: (type?: NotificationTypeFilters) => void,
70
+ }
71
+
72
+ interface NotificationFilterButtonProps extends NotificationsFilterProps {
73
+ ariaLabel: string,
74
+ label: string,
75
+ enumType: NotificationTypeFilters,
76
+ }
77
+
78
+ const NotificationFilterButton = (props: NotificationFilterButtonProps) => {
79
+ const { type, onChangeFilterType, ariaLabel, label, enumType } = props
80
+ return (<Button
81
+ appearance="text"
82
+ role="button"
83
+ aria-label={ariaLabel}
84
+ aria-pressed={type === enumType}
85
+ onClick={() => onChangeFilterType(enumType)}
86
+ sx={{ borderColor: type === enumType ? 'primary' : 'transparent' }}
87
+ >
88
+ <Text colorScheme="inverse" appearance="microtext1">
89
+ {label}
90
+ </Text>
91
+ </Button>
92
+ )
93
+ }
94
+
95
+ const NotificationsFilter = ({ type, onChangeFilterType }: NotificationsFilterProps) => {
96
+ const t = useTranslate(dictionary)
97
+
98
+ return (<Flex alignItems="center" sx={{ gap: '4px' }} my="5">
99
+ <Button
100
+ aria-pressed={!type}
101
+ appearance="text"
102
+ role="button"
103
+ aria-label={t.filterAll}
104
+ onClick={() => onChangeFilterType()}
105
+ sx={{ borderColor: !type ? 'primary' : 'transparent' }}
106
+ >
107
+ <Text colorScheme="inverse" appearance="microtext1">
108
+ {t.all}
109
+ </Text>
110
+ </Button>
111
+ <NotificationFilterButton
112
+ type={type} onChangeFilterType={onChangeFilterType}
113
+ ariaLabel={t.filterUnread} label={t.unread} enumType={UnreadType.Unread}
114
+ />
115
+ <NotificationFilterButton
116
+ type={type} onChangeFilterType={onChangeFilterType}
117
+ ariaLabel={t.filterHigh} label={t.high} enumType={NotificationType.High}
118
+ />
119
+ <NotificationFilterButton
120
+ type={type} onChangeFilterType={onChangeFilterType}
121
+ ariaLabel={t.filterMedium} label={t.medium} enumType={NotificationType.Medium}
122
+ />
123
+ <NotificationFilterButton
124
+ type={type} onChangeFilterType={onChangeFilterType}
125
+ ariaLabel={t.filterLow} label={t.low} enumType={NotificationType.Low}
126
+ />
127
+ </Flex>)
128
+ }
129
+
130
+ interface Props {
131
+ onMarkAsRead: (notificationId: string, type: 'callToAction' | 'icon') => void,
132
+ notifications?: StackspotNotification[],
133
+ isLoading: boolean,
134
+ error?: any,
135
+ onClickViewNotifications: () => void,
136
+ onClickViewAll: () => void,
137
+ errorDetails: ErrorProps,
138
+ fetchNextPage: () => void,
139
+ hasNextPage: boolean,
140
+ type?: NotificationTypeFilters,
141
+ onUpdateType: (updatedType?: NotificationTypeFilters) => void,
142
+ placeholderComponent: ReactElement,
143
+ isSummary: boolean,
144
+ }
145
+
146
+ export const NotificationComponent = ({
147
+ hasUnreadNotification, onMarkAsRead, notifications, isLoading, error,
148
+ type, onUpdateType,
149
+ onClickViewNotifications, onClickViewAll, errorDetails, fetchNextPage, hasNextPage,
150
+ placeholderComponent, isSummary=false,
151
+ }: Props) => {
152
+ const t = useTranslate(dictionary)
153
+ const [visible, setVisible] = useState(false)
154
+
155
+ const updateType = (updatedType?: NotificationTypeFilters) =>{
156
+ onUpdateType(updatedType)
157
+ }
158
+
159
+ return (<Flex sx={{ position: 'relative' }}>
160
+ <IconBox size="md" sx={{ cursor: 'pointer' }}
161
+ onClick={() => {
162
+ onClickViewNotifications()
163
+ setVisible(true)
164
+ }} className="notificationsTour">
165
+ <Bell />
166
+ </IconBox>
167
+ {hasUnreadNotification && <Box sx={{ position: 'absolute', right: '2px' }}>
168
+ <StatusCircle status={'danger'} />
169
+ </Box>}
170
+
171
+ <NotificationsComponent
172
+ className={listToClass(['selection-list', visible ? 'visible' : undefined])}
173
+ $scroll={true}
174
+ aria-hidden={!visible}
175
+ >
176
+ <Flex className="content" p={5} flexDirection="column" justifyContent="space-between">
177
+ <Flex>
178
+ <Flex justifyContent="space-between" w="100%" >
179
+ <Text appearance="h4">
180
+ {t.notifications}
181
+ </Text>
182
+ <IconBox appearance="circle" onClick={() => setVisible(false)}
183
+ sx={{ border: `1px solid ${theme.color.light[500]}`, cursor: 'pointer' }}>
184
+ <TimesMini />
185
+ </IconBox>
186
+ </Flex>
187
+
188
+ <NotificationsFilter type={type} onChangeFilterType={updateType} />
189
+
190
+ <AsyncContent error={error} errorDetails={errorDetails} loading={isLoading}>
191
+ {notifications?.length ? <StyledBox>
192
+ <InfiniteScroll
193
+ dataLength={notifications?.length || 0}
194
+ next={fetchNextPage}
195
+ hasMore={hasNextPage}
196
+ >
197
+ <ScrollView direction="vertical" style={{ maxHeight: '630px' }}>
198
+ <Flex sx={{ gap: '4px' }} mr="3">
199
+ {notifications?.map((item) => (
200
+ <NotificationItem key={item.id} notification={item}
201
+ isSummary={isSummary} onClickMarkRead={(type) => onMarkAsRead(item.id, type)} />
202
+ ))}
203
+ </Flex>
204
+ </ScrollView>
205
+ </InfiniteScroll>
206
+ </StyledBox>
207
+ :
208
+ <>
209
+ {placeholderComponent}
210
+ </>
211
+ }
212
+ </AsyncContent>
213
+ </Flex>
214
+
215
+ <Flex w="100%" pt={3}>
216
+ <Button
217
+ size="sm"
218
+ sx={{ width: '100%' }} colorScheme="inverse" appearance="text"
219
+ onClick={() => {
220
+ setVisible(false)
221
+ onClickViewAll()
222
+ }}>
223
+ <Text appearance="microtext1">
224
+ {t.seeAll}
225
+ </Text>
226
+ </Button>
227
+ </Flex>
228
+ </Flex>
229
+
230
+
231
+ </NotificationsComponent>
232
+ </Flex>)
233
+ }
234
+
235
+ const dictionary = {
236
+ en: {
237
+ notifications: 'Notifications',
238
+ all: 'All',
239
+ filterAll: 'Filter all notifications',
240
+ filterUnread: 'Filter unread notifications',
241
+ filterHigh: 'Filter high notifications',
242
+ filterMedium: 'Filter medium notifications',
243
+ filterLow: 'Filter low notifications',
244
+ unread: 'Unread',
245
+ high: 'High',
246
+ medium: 'Medium',
247
+ low: 'Low',
248
+ seeAll: 'See all notifications',
249
+ },
250
+ pt: {
251
+ notifications: 'Notificações',
252
+ all: 'Todos',
253
+ filterAll: 'Filtrar todas as notificações ',
254
+ filterUnread: 'Filtrar notificações não lidas',
255
+ filterHigh: 'Filtrar notificações criticidade alta',
256
+ filterMedium: 'Filtrar notificações criticidade média',
257
+ filterLow: 'Filtrar notificações criticidade baixa',
258
+ unread: 'Não lidas',
259
+ high: 'Alto',
260
+ medium: 'Médio',
261
+ low: 'Baixo',
262
+ seeAll: 'Ver todas as notificações',
263
+ },
264
+ } satisfies Dictionary