@stack-spot/portal-layout 1.0.2 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (68) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/dist/Layout.d.ts +2 -2
  3. package/dist/Layout.js +1 -1
  4. package/dist/LayoutOverlayManager.js +6 -6
  5. package/dist/LayoutOverlayManager.js.map +1 -1
  6. package/dist/components/Dialog.d.ts +1 -1
  7. package/dist/components/Dialog.js +1 -1
  8. package/dist/components/Header.d.ts +1 -1
  9. package/dist/components/Header.js +1 -1
  10. package/dist/components/OverlayContent.d.ts +1 -1
  11. package/dist/components/OverlayContent.js +20 -20
  12. package/dist/components/PortalSwitcher.d.ts +1 -1
  13. package/dist/components/PortalSwitcher.js +54 -54
  14. package/dist/components/Toaster.d.ts +2 -2
  15. package/dist/components/Toaster.js +1 -1
  16. package/dist/components/UserMenu.d.ts +1 -1
  17. package/dist/components/UserMenu.d.ts.map +1 -1
  18. package/dist/components/UserMenu.js +44 -42
  19. package/dist/components/UserMenu.js.map +1 -1
  20. package/dist/components/error/ErrorBoundary.d.ts +1 -1
  21. package/dist/components/error/ErrorBoundary.js +1 -1
  22. package/dist/components/error/SilentErrorBoundary.d.ts +1 -1
  23. package/dist/components/error/SilentErrorBoundary.js +1 -1
  24. package/dist/components/menu/MenuContent.d.ts +2 -2
  25. package/dist/components/menu/MenuContent.js +123 -123
  26. package/dist/components/menu/MenuContent.js.map +1 -1
  27. package/dist/components/menu/MenuSections.d.ts +1 -1
  28. package/dist/components/menu/MenuSections.js +1 -1
  29. package/dist/components/menu/MenuSections.js.map +1 -1
  30. package/dist/components/menu/PageSelector.d.ts +1 -1
  31. package/dist/components/menu/PageSelector.js +69 -69
  32. package/dist/components/menu/PageSelector.js.map +1 -1
  33. package/dist/components/tour/PortalSwitcherStep.js +1 -1
  34. package/dist/components/user-menu-manager.d.ts +13 -0
  35. package/dist/components/user-menu-manager.d.ts.map +1 -0
  36. package/dist/components/user-menu-manager.js +36 -0
  37. package/dist/components/user-menu-manager.js.map +1 -0
  38. package/dist/layout.css +477 -477
  39. package/dist/toaster.js +1 -1
  40. package/package.json +9 -6
  41. package/readme.md +146 -146
  42. package/src/Layout.tsx +171 -171
  43. package/src/LayoutOverlayManager.tsx +464 -464
  44. package/src/components/Dialog.tsx +140 -140
  45. package/src/components/Header.tsx +62 -62
  46. package/src/components/OverlayContent.tsx +80 -80
  47. package/src/components/PortalSwitcher.tsx +161 -161
  48. package/src/components/Toaster.tsx +95 -95
  49. package/src/components/UserMenu.tsx +127 -124
  50. package/src/components/error/ErrorBoundary.tsx +47 -47
  51. package/src/components/error/ErrorManager.ts +47 -47
  52. package/src/components/error/SilentErrorBoundary.tsx +64 -64
  53. package/src/components/menu/MenuContent.tsx +270 -270
  54. package/src/components/menu/MenuSections.tsx +320 -320
  55. package/src/components/menu/PageSelector.tsx +164 -164
  56. package/src/components/menu/constants.ts +2 -2
  57. package/src/components/menu/types.ts +205 -205
  58. package/src/components/tour/PortalSwitcherStep.tsx +39 -39
  59. package/src/components/types.ts +1 -1
  60. package/src/components/user-menu-manager.ts +31 -0
  61. package/src/dictionary.ts +28 -28
  62. package/src/elements.ts +30 -30
  63. package/src/errors.ts +11 -11
  64. package/src/index.ts +14 -14
  65. package/src/layout.css +477 -477
  66. package/src/toaster.tsx +153 -153
  67. package/src/utils.ts +29 -29
  68. package/tsconfig.json +8 -8
@@ -1,161 +1,161 @@
1
- import { Button, Flex, IconBox, Text } from '@citric/core'
2
- import { ArrowRight, CheckCircleFill, Select } from '@citric/icons'
3
- import { SelectionList } from '@stack-spot/portal-components/SelectionList'
4
- import { AI, EDP, HUB, Logo } from '@stack-spot/portal-components/svg'
5
- import { theme } from '@stack-spot/portal-theme'
6
- import { useTranslate } from '@stack-spot/portal-translate'
7
- import { ReactNode, useState } from 'react'
8
- import styled from 'styled-components'
9
- import { announce } from '../utils'
10
- import { PortalAcronym } from './types'
11
-
12
- const Logos: Record<PortalAcronym, ReactNode> = {
13
- 'AI': <AI />,
14
- 'EDP': <EDP />,
15
- 'HUB': <HUB />,
16
- }
17
-
18
- export interface Portal {
19
- /**
20
- * A Stackspot Portal.
21
- */
22
- acronym: PortalAcronym,
23
- /**
24
- * The URL to the Stackspot Portal.
25
- */
26
- url: string,
27
- }
28
-
29
- export interface PortalSwitcherProps {
30
- /**
31
- * The Stackspot portals to show in the selector.
32
- */
33
- portals?: Portal[],
34
- }
35
-
36
- const PortalSwitcherBox = styled(Flex)`
37
- flex-direction: column;
38
- align-items: start;
39
- z-index: 10;
40
-
41
- .current-portal {
42
- padding: 8px;
43
- border-radius: 4px;
44
- cursor: pointer;
45
- &:hover {
46
- background-color: ${theme.color.light[500]};
47
- }
48
- }
49
-
50
- .selection-list {
51
- max-width: 360px;
52
- box-shadow: 4px 4px 48px 0px #000000;
53
- position: absolute;
54
- top: 50px;
55
-
56
- .selection-list-content {
57
- padding: 8px;
58
- border-width: 1px;
59
- border-style: solid;
60
- border-color: ${theme.color.light['500']};
61
-
62
- &> ul {
63
- display: flex;
64
- flex-direction: column;
65
- gap: 8px;
66
- }
67
-
68
- .action {
69
- padding: 16px;
70
- background-color: ${theme.color.light['400']};
71
- border-width: 1px;
72
- border-style: solid;
73
- border-color: ${theme.color.light['500']};
74
- border-radius: 4px;
75
-
76
- &:hover, &:hover a {
77
- background-color: ${theme.color.light['500']};
78
- }
79
-
80
- a {
81
- height: auto;
82
- transition: unset;
83
- align-items: flex-start;
84
- }
85
- }
86
- }
87
- }
88
-
89
- `
90
- const PORTAL_SWITCHER_ID = 'PortalSwitcher'
91
-
92
- /**
93
- * A selector with different Stackspot portals.
94
- * Each item contains a logo with a link to the portal.
95
- * @param props the component Props {@link PortalSwitcherProps}.
96
- */
97
- export const PortalSwitcher = ({ portals = [] }: PortalSwitcherProps) => {
98
- const [visible, setVisible] = useState<boolean>(false)
99
- const t = useTranslate(translations)
100
- const currentPortal = portals?.find(portal => location.href.startsWith(portal.url))
101
-
102
- return <PortalSwitcherBox>
103
- {currentPortal ?
104
- <Button
105
- className="current-portal"
106
- appearance="text"
107
- colorScheme="light"
108
- aria-controls={PORTAL_SWITCHER_ID}
109
- aria-expanded={visible}
110
- aria-label={`${t.portalSwitcher}: ${currentPortal?.acronym} ${t.selected}`}
111
- onClick={() => {
112
- setVisible(true)
113
- announce(`${t.portalSwitcher} ${t.selected}`)
114
- }}>
115
- <Flex alignItems="center" className="portal-switcher">
116
- {Logos[currentPortal.acronym]}
117
- <IconBox size="xs" ml={3}>
118
- <Select />
119
- </IconBox>
120
- </Flex>
121
- </Button> :
122
- <Logo />}
123
- <SelectionList
124
- id={PORTAL_SWITCHER_ID}
125
- items={portals?.map(portal => ({
126
- label: {
127
- id: portal.acronym,
128
- element: <Flex flexDirection="column">
129
- {Logos[portal.acronym]}
130
- <Text appearance="microtext1" mt={3} colorScheme="light.700">{t[portal.acronym]}</Text>
131
- </Flex>,
132
- },
133
- target: '_self',
134
- href: portal.url,
135
- active: currentPortal?.acronym == portal.acronym,
136
- iconActive: <CheckCircleFill aria-label={t.selected} />,
137
- iconRight: portal.acronym !== currentPortal?.acronym ? <ArrowRight /> : undefined,
138
- }))}
139
- visible={visible}
140
- maxHeight="21rem"
141
- onHide={() => setVisible(false)} />
142
- </PortalSwitcherBox >
143
- }
144
-
145
-
146
- const translations = {
147
- en: {
148
- EDP: 'Efficient and secure solutions from code to production deployment.',
149
- AI: 'Speed up coding with efficient suggestions and high-quality results.',
150
- HUB: 'Discover AI Stacks, knowledge sources, and quick commands, all in one streamlined hub.',
151
- portalSwitcher: 'Portal switcher',
152
- selected: 'selected',
153
- },
154
- pt: {
155
- EDP: 'Soluções eficientes e seguras do código até a implantação em produção.',
156
- AI: 'Acelere o desenvolvimento com sugestões eficientes e resultados de alta qualidade.',
157
- HUB: 'Descubra AI Stacks, knowledge sources e quick commands, tudo em um hub simplificado.',
158
- portalSwitcher: 'Seletor de portais',
159
- selected: 'selecionado',
160
- },
161
- }
1
+ import { Button, Flex, IconBox, Text } from '@citric/core'
2
+ import { ArrowRight, CheckCircleFill, Select } from '@citric/icons'
3
+ import { SelectionList } from '@stack-spot/portal-components/SelectionList'
4
+ import { AI, EDP, HUB, Logo } from '@stack-spot/portal-components/svg'
5
+ import { theme } from '@stack-spot/portal-theme'
6
+ import { useTranslate } from '@stack-spot/portal-translate'
7
+ import { ReactNode, useState } from 'react'
8
+ import styled from 'styled-components'
9
+ import { announce } from '../utils'
10
+ import { PortalAcronym } from './types'
11
+
12
+ const Logos: Record<PortalAcronym, ReactNode> = {
13
+ 'AI': <AI />,
14
+ 'EDP': <EDP />,
15
+ 'HUB': <HUB />,
16
+ }
17
+
18
+ export interface Portal {
19
+ /**
20
+ * A Stackspot Portal.
21
+ */
22
+ acronym: PortalAcronym,
23
+ /**
24
+ * The URL to the Stackspot Portal.
25
+ */
26
+ url: string,
27
+ }
28
+
29
+ export interface PortalSwitcherProps {
30
+ /**
31
+ * The Stackspot portals to show in the selector.
32
+ */
33
+ portals?: Portal[],
34
+ }
35
+
36
+ const PortalSwitcherBox = styled(Flex)`
37
+ flex-direction: column;
38
+ align-items: start;
39
+ z-index: 10;
40
+
41
+ .current-portal {
42
+ padding: 8px;
43
+ border-radius: 4px;
44
+ cursor: pointer;
45
+ &:hover {
46
+ background-color: ${theme.color.light[500]};
47
+ }
48
+ }
49
+
50
+ .selection-list {
51
+ max-width: 360px;
52
+ box-shadow: 4px 4px 48px 0px #000000;
53
+ position: absolute;
54
+ top: 50px;
55
+
56
+ .selection-list-content {
57
+ padding: 8px;
58
+ border-width: 1px;
59
+ border-style: solid;
60
+ border-color: ${theme.color.light['500']};
61
+
62
+ &> ul {
63
+ display: flex;
64
+ flex-direction: column;
65
+ gap: 8px;
66
+ }
67
+
68
+ .action {
69
+ padding: 16px;
70
+ background-color: ${theme.color.light['400']};
71
+ border-width: 1px;
72
+ border-style: solid;
73
+ border-color: ${theme.color.light['500']};
74
+ border-radius: 4px;
75
+
76
+ &:hover, &:hover a {
77
+ background-color: ${theme.color.light['500']};
78
+ }
79
+
80
+ a {
81
+ height: auto;
82
+ transition: unset;
83
+ align-items: flex-start;
84
+ }
85
+ }
86
+ }
87
+ }
88
+
89
+ `
90
+ const PORTAL_SWITCHER_ID = 'PortalSwitcher'
91
+
92
+ /**
93
+ * A selector with different Stackspot portals.
94
+ * Each item contains a logo with a link to the portal.
95
+ * @param props the component Props {@link PortalSwitcherProps}.
96
+ */
97
+ export const PortalSwitcher = ({ portals = [] }: PortalSwitcherProps) => {
98
+ const [visible, setVisible] = useState<boolean>(false)
99
+ const t = useTranslate(translations)
100
+ const currentPortal = portals?.find(portal => location.href.startsWith(portal.url))
101
+
102
+ return <PortalSwitcherBox>
103
+ {currentPortal ?
104
+ <Button
105
+ className="current-portal"
106
+ appearance="text"
107
+ colorScheme="light"
108
+ aria-controls={PORTAL_SWITCHER_ID}
109
+ aria-expanded={visible}
110
+ aria-label={`${t.portalSwitcher}: ${currentPortal?.acronym} ${t.selected}`}
111
+ onClick={() => {
112
+ setVisible(true)
113
+ announce(`${t.portalSwitcher} ${t.selected}`)
114
+ }}>
115
+ <Flex alignItems="center" className="portal-switcher">
116
+ {Logos[currentPortal.acronym]}
117
+ <IconBox size="xs" ml={3}>
118
+ <Select />
119
+ </IconBox>
120
+ </Flex>
121
+ </Button> :
122
+ <Logo />}
123
+ <SelectionList
124
+ id={PORTAL_SWITCHER_ID}
125
+ items={portals?.map(portal => ({
126
+ label: {
127
+ id: portal.acronym,
128
+ element: <Flex flexDirection="column">
129
+ {Logos[portal.acronym]}
130
+ <Text appearance="microtext1" mt={3} colorScheme="light.700">{t[portal.acronym]}</Text>
131
+ </Flex>,
132
+ },
133
+ target: '_self',
134
+ href: portal.url,
135
+ active: currentPortal?.acronym == portal.acronym,
136
+ iconActive: <CheckCircleFill aria-label={t.selected} />,
137
+ iconRight: portal.acronym !== currentPortal?.acronym ? <ArrowRight /> : undefined,
138
+ }))}
139
+ visible={visible}
140
+ maxHeight="21rem"
141
+ onHide={() => setVisible(false)} />
142
+ </PortalSwitcherBox >
143
+ }
144
+
145
+
146
+ const translations = {
147
+ en: {
148
+ EDP: 'Efficient and secure solutions from code to production deployment.',
149
+ AI: 'Speed up coding with efficient suggestions and high-quality results.',
150
+ HUB: 'Discover AI Stacks, knowledge sources, and quick commands, all in one streamlined hub.',
151
+ portalSwitcher: 'Portal switcher',
152
+ selected: 'selected',
153
+ },
154
+ pt: {
155
+ EDP: 'Soluções eficientes e seguras do código até a implantação em produção.',
156
+ AI: 'Acelere o desenvolvimento com sugestões eficientes e resultados de alta qualidade.',
157
+ HUB: 'Descubra AI Stacks, knowledge sources e quick commands, tudo em um hub simplificado.',
158
+ portalSwitcher: 'Seletor de portais',
159
+ selected: 'selecionado',
160
+ },
161
+ }
@@ -1,95 +1,95 @@
1
- import { Flex, Text } from '@citric/core'
2
- import { TimesMini } from '@citric/icons'
3
- import { IconButton } from '@citric/ui'
4
- import { useAnchorTag } from '@stack-spot/portal-components/anchor'
5
- import { useMemo } from 'react'
6
- import type { CloseButton as DefaultCloseButton } from 'react-toastify'
7
- import { ToastContainer, toast } from 'react-toastify'
8
- import 'react-toastify/dist/ReactToastify.css'
9
- import { useDictionary } from '../dictionary'
10
-
11
- type CloseButtonProps = Parameters<typeof DefaultCloseButton>[0]
12
-
13
- export const TOASTER_CLOSE_BTN_CLASS = 'btn-close'
14
-
15
- const CloseButton = ({ closeToast }: CloseButtonProps) => {
16
- const t = useDictionary()
17
- return (
18
- <IconButton className={TOASTER_CLOSE_BTN_CLASS} onClick={closeToast} title={t.dismiss}>
19
- <TimesMini />
20
- </IconButton>
21
- )
22
- }
23
-
24
- /**
25
- * Uses react-toastify to render a Toaster based on the Citric DS.
26
- */
27
- export const Toaster = () => <ToastContainer closeButton={CloseButton} />
28
-
29
- export interface ToasterAction {
30
- /**
31
- * The button's label.
32
- */
33
- label: string,
34
- /**
35
- * A function to run once the button is clicked.
36
- */
37
- onClick?: (event: React.MouseEvent) => void,
38
- /**
39
- * If this is set, instead of a button, an anchor is rendered with this href.
40
- */
41
- href?: string,
42
- /**
43
- * Whether or not to close the toaster once the button is clicked.
44
- * @default true
45
- */
46
- closeOnClick?: boolean,
47
- }
48
-
49
- interface ToasterContentProps {
50
- id: number | string,
51
- actions?: ToasterAction[],
52
- onClick?: (event: React.MouseEvent) => void,
53
- title?: string,
54
- message: React.ReactNode,
55
- }
56
-
57
- const actionStyle: React.CSSProperties = {
58
- background: 'transparent',
59
- border: 'none',
60
- padding: 0,
61
- color: 'inherit',
62
- font: 'inherit',
63
- fontWeight: 500,
64
- cursor: 'pointer',
65
- }
66
-
67
- /**
68
- * Renders a toaster with the default layout for toasters.
69
- */
70
- export const ToasterContent = ({ id, message, actions, onClick, title }: ToasterContentProps) => {
71
- const Link = useAnchorTag()
72
- const buttons = useMemo(() => actions?.map(
73
- ({ label, href, onClick, closeOnClick = true }) => (
74
- <Text
75
- key={label}
76
- as={href ? Link : 'button'}
77
- href={href}
78
- style={actionStyle}
79
- onClick={(event: React.MouseEvent) => {
80
- onClick?.(event)
81
- if (closeOnClick) toast.dismiss(id)
82
- }}
83
- >
84
- {label}
85
- </Text>
86
- ),
87
- ), [actions])
88
- return (
89
- <div onClick={onClick}>
90
- <h1 style={{ textTransform: 'capitalize' }}>{title}</h1>
91
- {typeof message === 'string' ? <p>{message}</p> : message}
92
- {buttons?.length ? <Flex style={{ gap: '12px', marginTop: '12px' }}>{buttons}</Flex> : null}
93
- </div>
94
- )
95
- }
1
+ import { Flex, Text } from '@citric/core'
2
+ import { TimesMini } from '@citric/icons'
3
+ import { IconButton } from '@citric/ui'
4
+ import { useAnchorTag } from '@stack-spot/portal-components/anchor'
5
+ import { useMemo } from 'react'
6
+ import type { CloseButton as DefaultCloseButton } from 'react-toastify'
7
+ import { ToastContainer, toast } from 'react-toastify'
8
+ import 'react-toastify/dist/ReactToastify.css'
9
+ import { useDictionary } from '../dictionary'
10
+
11
+ type CloseButtonProps = Parameters<typeof DefaultCloseButton>[0]
12
+
13
+ export const TOASTER_CLOSE_BTN_CLASS = 'btn-close'
14
+
15
+ const CloseButton = ({ closeToast }: CloseButtonProps) => {
16
+ const t = useDictionary()
17
+ return (
18
+ <IconButton className={TOASTER_CLOSE_BTN_CLASS} onClick={closeToast} title={t.dismiss}>
19
+ <TimesMini />
20
+ </IconButton>
21
+ )
22
+ }
23
+
24
+ /**
25
+ * Uses react-toastify to render a Toaster based on the Citric DS.
26
+ */
27
+ export const Toaster = () => <ToastContainer closeButton={CloseButton} />
28
+
29
+ export interface ToasterAction {
30
+ /**
31
+ * The button's label.
32
+ */
33
+ label: string,
34
+ /**
35
+ * A function to run once the button is clicked.
36
+ */
37
+ onClick?: (event: React.MouseEvent) => void,
38
+ /**
39
+ * If this is set, instead of a button, an anchor is rendered with this href.
40
+ */
41
+ href?: string,
42
+ /**
43
+ * Whether or not to close the toaster once the button is clicked.
44
+ * @default true
45
+ */
46
+ closeOnClick?: boolean,
47
+ }
48
+
49
+ interface ToasterContentProps {
50
+ id: number | string,
51
+ actions?: ToasterAction[],
52
+ onClick?: (event: React.MouseEvent) => void,
53
+ title?: string,
54
+ message: React.ReactNode,
55
+ }
56
+
57
+ const actionStyle: React.CSSProperties = {
58
+ background: 'transparent',
59
+ border: 'none',
60
+ padding: 0,
61
+ color: 'inherit',
62
+ font: 'inherit',
63
+ fontWeight: 500,
64
+ cursor: 'pointer',
65
+ }
66
+
67
+ /**
68
+ * Renders a toaster with the default layout for toasters.
69
+ */
70
+ export const ToasterContent = ({ id, message, actions, onClick, title }: ToasterContentProps) => {
71
+ const Link = useAnchorTag()
72
+ const buttons = useMemo(() => actions?.map(
73
+ ({ label, href, onClick, closeOnClick = true }) => (
74
+ <Text
75
+ key={label}
76
+ as={href ? Link : 'button'}
77
+ href={href}
78
+ style={actionStyle}
79
+ onClick={(event: React.MouseEvent) => {
80
+ onClick?.(event)
81
+ if (closeOnClick) toast.dismiss(id)
82
+ }}
83
+ >
84
+ {label}
85
+ </Text>
86
+ ),
87
+ ), [actions])
88
+ return (
89
+ <div onClick={onClick}>
90
+ <h1 style={{ textTransform: 'capitalize' }}>{title}</h1>
91
+ {typeof message === 'string' ? <p>{message}</p> : message}
92
+ {buttons?.length ? <Flex style={{ gap: '12px', marginTop: '12px' }}>{buttons}</Flex> : null}
93
+ </div>
94
+ )
95
+ }