@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,164 +1,164 @@
1
- import { IconBox, Text } from '@citric/core'
2
- import { ArrowRight, Select } from '@citric/icons'
3
- import { LoadingCircular } from '@citric/ui'
4
- import { useCheckTextOverflow } from '@stack-spot/portal-components'
5
- import { ListAction, SelectionList } from '@stack-spot/portal-components/SelectionList'
6
- import { useAnchorTag } from '@stack-spot/portal-components/anchor'
7
- import { theme } from '@stack-spot/portal-theme'
8
- import { Dictionary, interpolate, useTranslate } from '@stack-spot/portal-translate'
9
- import { KeyboardEvent, useMemo, useRef, useState } from 'react'
10
- import { styled } from 'styled-components'
11
- import { MENU_CONTENT_PADDING as PADDING } from './constants'
12
- import { Selector } from './types'
13
-
14
- const SelectorBox = styled.div`
15
- position: relative;
16
- margin: ${PADDING}px;
17
- margin-bottom: 28px;
18
-
19
- > button {
20
- display: flex;
21
- gap: 8px;
22
- align-items: center;
23
- border-radius: 0.25rem;
24
- background-color: transparent;
25
- border: 1px solid ${theme.color.light['500']};
26
- padding: 8px;
27
- transition: background-color 0.2s;
28
- color: ${theme.color.light['contrastText']};
29
- width: 100%;
30
- text-align: left;
31
-
32
- &:hover {
33
- background-color: ${theme.color.light['500']};
34
- cursor: pointer;
35
- }
36
-
37
- .label {
38
- flex: 1;
39
- white-space: nowrap;
40
- overflow: hidden;
41
- text-overflow: ellipsis;
42
- }
43
- }
44
-
45
- .selection-list {
46
- position: absolute;
47
- left: 0;
48
- right: 0;
49
- box-shadow: none;
50
- border-radius: 0.25rem;
51
-
52
- .selection-list-content {
53
- padding: 8px;
54
- border-radius: 0.25rem;
55
- border: none;
56
- ul {
57
- display: flex;
58
- flex-direction: column;
59
- gap: 8px;
60
- }
61
- }
62
-
63
- li > a {
64
- border: 1px solid ${theme.color.light['500']};
65
- background-color: ${theme.color.light['400']};
66
-
67
- &:hover {
68
- background-color: ${theme.color.light['500']};
69
- }
70
- }
71
-
72
- .view-all {
73
- background: ${theme.color.light['500']};
74
- border-radius: 0.25rem;
75
- height: 40px;
76
- display: flex;
77
- align-items: center;
78
- justify-content: center;
79
- margin-top: 8px;
80
- }
81
- }
82
- `
83
-
84
- /**
85
- * A selector component to render inside a menu-content. Allows the user to select another page.
86
- *
87
- * @param props the React props for the component {@link Selector}.
88
- */
89
- export const PageSelector = ({ options, value, button, loading, title }: Selector) => {
90
- const Link = useAnchorTag()
91
- const t = useTranslate(dictionary)
92
- const [visible, setVisible] = useState(false)
93
- const { ref, overflow } = useCheckTextOverflow()
94
- const id = useRef(`pageSelector${title || Math.random()}`)
95
- const { optionsWithIcon, selected } = useMemo(
96
- () => {
97
- let selected = options[0]
98
- const optionsWithIcon = options.map<ListAction>((option) => {
99
- if (option.key === value) {
100
- selected = option
101
- return { ...option, active: true }
102
- }
103
- return { ...option, iconRight: <ArrowRight /> }
104
- })
105
- return { optionsWithIcon, selected }
106
- },
107
- [options, value, button],
108
- )
109
-
110
- const label = selected?.label ?? button?.label ?? value
111
- const isTextLabel = typeof label == 'string'
112
- const labelText = typeof label === 'string' ? label : label.id
113
- const buttonLabelText = typeof button?.label == 'string' ? button?.label : button?.label.id
114
-
115
- return (
116
- <SelectorBox>
117
- {loading
118
- ? <LoadingCircular />
119
- : (
120
- <>
121
- {title && <Text colorScheme="light.700" sx={{ mb: 3 }}>{title}</Text>}
122
- <button
123
- onClick={() => setVisible(true)}
124
- onKeyDown={(e: KeyboardEvent<HTMLButtonElement>) => e.key === 'Enter' && setVisible(true)}
125
- title={label as string}
126
- tabIndex={0}
127
- aria-label={interpolate(t.accessibility, [label])}
128
- aria-expanded={visible}
129
- aria-controls={id.current}
130
- aria-haspopup="listbox"
131
- >
132
- {selected?.icon && <IconBox>{selected?.icon}</IconBox>}
133
- {isTextLabel ?
134
- <Text ref={ref} appearance="body2" className="label" title={overflow ? labelText : ''}>{labelText}</Text> :
135
- label.element}
136
- <IconBox size="xs"><Select /></IconBox>
137
- </button>
138
-
139
- <SelectionList
140
- id={id.current}
141
- visible={visible}
142
- items={optionsWithIcon}
143
- onHide={() => setVisible(false)}
144
- after={button ?
145
- <Link className="view-all" tabIndex={0} href={button.href} onClick={button.onClick}>{buttonLabelText}</Link>
146
- : undefined}
147
- scroll
148
- />
149
- </>
150
- )
151
- }
152
- </SelectorBox>
153
- )
154
- }
155
-
156
- const dictionary = {
157
- en: {
158
- accessibility: 'Current value: $0.',
159
- },
160
- pt: {
161
- accessibility: 'Valor atual: $0.',
162
- },
163
- } satisfies Dictionary
164
-
1
+ import { IconBox, Text } from '@citric/core'
2
+ import { ArrowRight, Select } from '@citric/icons'
3
+ import { LoadingCircular } from '@citric/ui'
4
+ import { useCheckTextOverflow } from '@stack-spot/portal-components'
5
+ import { ListAction, SelectionList } from '@stack-spot/portal-components/SelectionList'
6
+ import { useAnchorTag } from '@stack-spot/portal-components/anchor'
7
+ import { theme } from '@stack-spot/portal-theme'
8
+ import { Dictionary, interpolate, useTranslate } from '@stack-spot/portal-translate'
9
+ import { KeyboardEvent, useMemo, useRef, useState } from 'react'
10
+ import { styled } from 'styled-components'
11
+ import { MENU_CONTENT_PADDING as PADDING } from './constants'
12
+ import { Selector } from './types'
13
+
14
+ const SelectorBox = styled.div`
15
+ position: relative;
16
+ margin: ${PADDING}px;
17
+ margin-bottom: 28px;
18
+
19
+ > button {
20
+ display: flex;
21
+ gap: 8px;
22
+ align-items: center;
23
+ border-radius: 0.25rem;
24
+ background-color: transparent;
25
+ border: 1px solid ${theme.color.light['500']};
26
+ padding: 8px;
27
+ transition: background-color 0.2s;
28
+ color: ${theme.color.light['contrastText']};
29
+ width: 100%;
30
+ text-align: left;
31
+
32
+ &:hover {
33
+ background-color: ${theme.color.light['500']};
34
+ cursor: pointer;
35
+ }
36
+
37
+ .label {
38
+ flex: 1;
39
+ white-space: nowrap;
40
+ overflow: hidden;
41
+ text-overflow: ellipsis;
42
+ }
43
+ }
44
+
45
+ .selection-list {
46
+ position: absolute;
47
+ left: 0;
48
+ right: 0;
49
+ box-shadow: none;
50
+ border-radius: 0.25rem;
51
+
52
+ .selection-list-content {
53
+ padding: 8px;
54
+ border-radius: 0.25rem;
55
+ border: none;
56
+ ul {
57
+ display: flex;
58
+ flex-direction: column;
59
+ gap: 8px;
60
+ }
61
+ }
62
+
63
+ li > a {
64
+ border: 1px solid ${theme.color.light['500']};
65
+ background-color: ${theme.color.light['400']};
66
+
67
+ &:hover {
68
+ background-color: ${theme.color.light['500']};
69
+ }
70
+ }
71
+
72
+ .view-all {
73
+ background: ${theme.color.light['500']};
74
+ border-radius: 0.25rem;
75
+ height: 40px;
76
+ display: flex;
77
+ align-items: center;
78
+ justify-content: center;
79
+ margin-top: 8px;
80
+ }
81
+ }
82
+ `
83
+
84
+ /**
85
+ * A selector component to render inside a menu-content. Allows the user to select another page.
86
+ *
87
+ * @param props the React props for the component {@link Selector}.
88
+ */
89
+ export const PageSelector = ({ options, value, button, loading, title }: Selector) => {
90
+ const Link = useAnchorTag()
91
+ const t = useTranslate(dictionary)
92
+ const [visible, setVisible] = useState(false)
93
+ const { ref, overflow } = useCheckTextOverflow()
94
+ const id = useRef(`pageSelector${title || Math.random()}`)
95
+ const { optionsWithIcon, selected } = useMemo(
96
+ () => {
97
+ let selected = options[0]
98
+ const optionsWithIcon = options.map<ListAction>((option) => {
99
+ if (option.key === value) {
100
+ selected = option
101
+ return { ...option, active: true }
102
+ }
103
+ return { ...option, iconRight: <ArrowRight /> }
104
+ })
105
+ return { optionsWithIcon, selected }
106
+ },
107
+ [options, value, button],
108
+ )
109
+
110
+ const label = selected?.label ?? button?.label ?? value
111
+ const isTextLabel = typeof label == 'string'
112
+ const labelText = typeof label === 'string' ? label : label.id
113
+ const buttonLabelText = typeof button?.label == 'string' ? button?.label : button?.label.id
114
+
115
+ return (
116
+ <SelectorBox>
117
+ {loading
118
+ ? <LoadingCircular />
119
+ : (
120
+ <>
121
+ {title && <Text colorScheme="light.700" sx={{ mb: 3 }}>{title}</Text>}
122
+ <button
123
+ onClick={() => setVisible(true)}
124
+ onKeyDown={(e: KeyboardEvent<HTMLButtonElement>) => e.key === 'Enter' && setVisible(true)}
125
+ title={label as string}
126
+ tabIndex={0}
127
+ aria-label={interpolate(t.accessibility, [label])}
128
+ aria-expanded={visible}
129
+ aria-controls={id.current}
130
+ aria-haspopup="listbox"
131
+ >
132
+ {selected?.icon && <IconBox>{selected?.icon}</IconBox>}
133
+ {isTextLabel ?
134
+ <Text ref={ref} appearance="body2" className="label" title={overflow ? labelText : ''}>{labelText}</Text> :
135
+ label.element}
136
+ <IconBox size="xs"><Select /></IconBox>
137
+ </button>
138
+
139
+ <SelectionList
140
+ id={id.current}
141
+ visible={visible}
142
+ items={optionsWithIcon}
143
+ onHide={() => setVisible(false)}
144
+ after={button ?
145
+ <Link className="view-all" tabIndex={0} href={button.href} onClick={button.onClick}>{buttonLabelText}</Link>
146
+ : undefined}
147
+ scroll
148
+ />
149
+ </>
150
+ )
151
+ }
152
+ </SelectorBox>
153
+ )
154
+ }
155
+
156
+ const dictionary = {
157
+ en: {
158
+ accessibility: 'Current value: $0.',
159
+ },
160
+ pt: {
161
+ accessibility: 'Valor atual: $0.',
162
+ },
163
+ } satisfies Dictionary
164
+
@@ -1,2 +1,2 @@
1
- export const MENU_CONTENT_PADDING = 20
2
- export const MENU_CONTENT_ITEM_PADDING = 16
1
+ export const MENU_CONTENT_PADDING = 20
2
+ export const MENU_CONTENT_ITEM_PADDING = 16