@stack-spot/portal-layout 0.0.1

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 (110) hide show
  1. package/dist/Layout.d.ts +22 -0
  2. package/dist/Layout.d.ts.map +1 -0
  3. package/dist/Layout.js +21 -0
  4. package/dist/Layout.js.map +1 -0
  5. package/dist/LayoutOverlayManager.d.ts +32 -0
  6. package/dist/LayoutOverlayManager.d.ts.map +1 -0
  7. package/dist/LayoutOverlayManager.js +154 -0
  8. package/dist/LayoutOverlayManager.js.map +1 -0
  9. package/dist/components/BottomNotification.d.ts +1 -0
  10. package/dist/components/BottomNotification.d.ts.map +1 -0
  11. package/dist/components/BottomNotification.js +2 -0
  12. package/dist/components/BottomNotification.js.map +1 -0
  13. package/dist/components/BottomPanel.d.ts +1 -0
  14. package/dist/components/BottomPanel.d.ts.map +1 -0
  15. package/dist/components/BottomPanel.js +2 -0
  16. package/dist/components/BottomPanel.js.map +1 -0
  17. package/dist/components/Dialog.d.ts +21 -0
  18. package/dist/components/Dialog.d.ts.map +1 -0
  19. package/dist/components/Dialog.js +19 -0
  20. package/dist/components/Dialog.js.map +1 -0
  21. package/dist/components/Header.d.ts +12 -0
  22. package/dist/components/Header.d.ts.map +1 -0
  23. package/dist/components/Header.js +6 -0
  24. package/dist/components/Header.js.map +1 -0
  25. package/dist/components/Logo.d.ts +2 -0
  26. package/dist/components/Logo.d.ts.map +1 -0
  27. package/dist/components/Logo.js +4 -0
  28. package/dist/components/Logo.js.map +1 -0
  29. package/dist/components/Menu/MenuContent.d.ts +3 -0
  30. package/dist/components/Menu/MenuContent.d.ts.map +1 -0
  31. package/dist/components/Menu/MenuContent.js +126 -0
  32. package/dist/components/Menu/MenuContent.js.map +1 -0
  33. package/dist/components/Menu/MenuSections.d.ts +3 -0
  34. package/dist/components/Menu/MenuSections.d.ts.map +1 -0
  35. package/dist/components/Menu/MenuSections.js +70 -0
  36. package/dist/components/Menu/MenuSections.js.map +1 -0
  37. package/dist/components/Menu/PageSelector.d.ts +3 -0
  38. package/dist/components/Menu/PageSelector.d.ts.map +1 -0
  39. package/dist/components/Menu/PageSelector.js +87 -0
  40. package/dist/components/Menu/PageSelector.js.map +1 -0
  41. package/dist/components/Menu/constants.d.ts +3 -0
  42. package/dist/components/Menu/constants.d.ts.map +1 -0
  43. package/dist/components/Menu/constants.js +3 -0
  44. package/dist/components/Menu/constants.js.map +1 -0
  45. package/dist/components/Menu/types.d.ts +45 -0
  46. package/dist/components/Menu/types.d.ts.map +1 -0
  47. package/dist/components/Menu/types.js +2 -0
  48. package/dist/components/Menu/types.js.map +1 -0
  49. package/dist/components/OverlayContent.d.ts +15 -0
  50. package/dist/components/OverlayContent.d.ts.map +1 -0
  51. package/dist/components/OverlayContent.js +26 -0
  52. package/dist/components/OverlayContent.js.map +1 -0
  53. package/dist/components/SelectionList.d.ts +34 -0
  54. package/dist/components/SelectionList.d.ts.map +1 -0
  55. package/dist/components/SelectionList.js +104 -0
  56. package/dist/components/SelectionList.js.map +1 -0
  57. package/dist/components/Toaster.d.ts +3 -0
  58. package/dist/components/Toaster.d.ts.map +1 -0
  59. package/dist/components/Toaster.js +8 -0
  60. package/dist/components/Toaster.js.map +1 -0
  61. package/dist/components/UserMenu.d.ts +9 -0
  62. package/dist/components/UserMenu.d.ts.map +1 -0
  63. package/dist/components/UserMenu.js +57 -0
  64. package/dist/components/UserMenu.js.map +1 -0
  65. package/dist/components/types.d.ts +6 -0
  66. package/dist/components/types.d.ts.map +1 -0
  67. package/dist/components/types.js +2 -0
  68. package/dist/components/types.js.map +1 -0
  69. package/dist/errors.d.ts +7 -0
  70. package/dist/errors.d.ts.map +1 -0
  71. package/dist/errors.js +11 -0
  72. package/dist/errors.js.map +1 -0
  73. package/dist/index.d.ts +11 -0
  74. package/dist/index.d.ts.map +1 -0
  75. package/dist/index.js +11 -0
  76. package/dist/index.js.map +1 -0
  77. package/dist/layout.css +383 -0
  78. package/dist/toaster.d.ts +23 -0
  79. package/dist/toaster.d.ts.map +1 -0
  80. package/dist/toaster.js +41 -0
  81. package/dist/toaster.js.map +1 -0
  82. package/dist/utils.d.ts +2 -0
  83. package/dist/utils.d.ts.map +1 -0
  84. package/dist/utils.js +8 -0
  85. package/dist/utils.js.map +1 -0
  86. package/package.json +39 -0
  87. package/src/Layout.tsx +68 -0
  88. package/src/LayoutOverlayManager.tsx +180 -0
  89. package/src/citric.fix.d.ts +7 -0
  90. package/src/components/BottomNotification.tsx +0 -0
  91. package/src/components/BottomPanel.tsx +0 -0
  92. package/src/components/Dialog.tsx +55 -0
  93. package/src/components/Header.tsx +23 -0
  94. package/src/components/Logo.tsx +35 -0
  95. package/src/components/Menu/MenuContent.tsx +179 -0
  96. package/src/components/Menu/MenuSections.tsx +105 -0
  97. package/src/components/Menu/PageSelector.tsx +108 -0
  98. package/src/components/Menu/constants.ts +2 -0
  99. package/src/components/Menu/types.ts +53 -0
  100. package/src/components/OverlayContent.tsx +50 -0
  101. package/src/components/SelectionList.tsx +200 -0
  102. package/src/components/Toaster.tsx +12 -0
  103. package/src/components/UserMenu.tsx +91 -0
  104. package/src/components/types.ts +5 -0
  105. package/src/errors.ts +11 -0
  106. package/src/index.ts +10 -0
  107. package/src/layout.css +383 -0
  108. package/src/toaster.tsx +72 -0
  109. package/src/utils.ts +7 -0
  110. package/tsconfig.json +8 -0
@@ -0,0 +1,108 @@
1
+ import { IconBox, Text } from '@citric/core'
2
+ import { ArrowRight, Select } from '@citric/icons'
3
+ import { theme } from '@stack-spot/portal-theme'
4
+ import { useMemo, useState } from 'react'
5
+ import { styled } from 'styled-components'
6
+ import { ListAction, SelectionList } from '../SelectionList'
7
+ import { MENU_CONTENT_PADDING as PADDING } from './constants'
8
+ import { Selector } from './types'
9
+
10
+ const SelectorBox = styled.div`
11
+ position: relative;
12
+ margin: ${PADDING}px;
13
+ margin-bottom: 28px;
14
+
15
+ > a {
16
+ display: flex;
17
+ gap: 8px;
18
+ align-items: center;
19
+ border-radius: 0.25rem;
20
+ border: 1px solid ${theme.color.light['500']};
21
+ padding: 8px;
22
+ transition: background-color 0.2s;
23
+
24
+ &:hover {
25
+ background-color: ${theme.color.light['500']};
26
+ }
27
+
28
+ .label {
29
+ flex: 1;
30
+ }
31
+ }
32
+
33
+ .selection-list {
34
+ position: absolute;
35
+ top: 0;
36
+ left: 0;
37
+ right: 0;
38
+ box-shadow: none;
39
+ border-radius: 0.25rem;
40
+
41
+ .selection-list-content {
42
+ padding: 8px;
43
+ border-radius: 0.25rem;
44
+ border: none;
45
+ ul {
46
+ display: flex;
47
+ flex-direction: column;
48
+ gap: 8px;
49
+ }
50
+ }
51
+
52
+ li > a {
53
+ border: 1px solid ${theme.color.light['500']};
54
+ background-color: ${theme.color.light['400']};
55
+
56
+ &:hover {
57
+ background-color: ${theme.color.light['500']};
58
+ }
59
+ }
60
+
61
+ .view-all {
62
+ background: ${theme.color.light['500']};
63
+ border-radius: 0.25rem;
64
+ outline: none;
65
+ height: 40px;
66
+ display: flex;
67
+ align-items: center;
68
+ justify-content: center;
69
+ margin-top: 8px;
70
+ }
71
+ }
72
+ `
73
+
74
+ export const PageSelector = ({ options, value, button }: Selector) => {
75
+ const [visible, setVisible] = useState(false)
76
+ const { optionsWithIcon, selected } = useMemo(
77
+ () => {
78
+ let selected = options[0]
79
+ const optionsWithIcon = options.map<ListAction>((option) => {
80
+ if (option.key === value) {
81
+ selected = option
82
+ return { ...option, active: true }
83
+ }
84
+ return { ...option, iconRight: <ArrowRight /> }
85
+ })
86
+ return { optionsWithIcon, selected }
87
+ },
88
+ [options, value, button],
89
+ )
90
+
91
+
92
+ return (
93
+ <SelectorBox>
94
+ <a onClick={() => setVisible(true)} aria-label={value}>
95
+ {selected?.icon && <IconBox>{selected?.icon}</IconBox>}
96
+ <Text appearance="body2" className="label">{selected?.label ?? button?.label ?? value}</Text>
97
+ <IconBox size="xs"><Select /></IconBox>
98
+ </a>
99
+
100
+ <SelectionList
101
+ visible={visible}
102
+ items={optionsWithIcon}
103
+ onHide={() => setVisible(false)}
104
+ after={button ? <a className="view-all" href={button.href} onClick={button.onClick}>{button.label}</a> : undefined}
105
+ />
106
+ </SelectorBox>
107
+ )
108
+ }
@@ -0,0 +1,2 @@
1
+ export const MENU_CONTENT_PADDING = 20
2
+ export const MENU_CONTENT_ITEM_PADDING = 12
@@ -0,0 +1,53 @@
1
+ import { ReactElement } from 'react'
2
+ import { Action } from '../types'
3
+
4
+ export interface ItemGroup {
5
+ label: string,
6
+ children: MenuItem[],
7
+ open?: boolean,
8
+ hidden?: boolean,
9
+ }
10
+
11
+ export interface MenuAction extends Action {
12
+ active?: boolean,
13
+ hidden?: boolean,
14
+ }
15
+
16
+ export type MenuItem = ItemGroup | MenuAction
17
+
18
+ export interface MenuButton extends Action {
19
+ icon?: ReactElement,
20
+ }
21
+
22
+ export interface SelectorItem extends Action {
23
+ key: string,
24
+ icon?: ReactElement,
25
+ }
26
+
27
+ export interface Selector {
28
+ value?: string,
29
+ options: SelectorItem[],
30
+ button?: Action,
31
+ title?: string,
32
+ subtitle?: string,
33
+ }
34
+
35
+ export interface MenuSectionContent {
36
+ goBack?: Action,
37
+ title?: string,
38
+ subtitle?: string,
39
+ pageSelector?: Selector,
40
+ options?: MenuItem[],
41
+ }
42
+
43
+ export interface MenuSection extends Action {
44
+ icon: ReactElement,
45
+ content?: MenuSectionContent,
46
+ active?: boolean,
47
+ }
48
+
49
+ export interface MenuProps {
50
+ sections: MenuSection[],
51
+ content?: MenuSectionContent,
52
+ compact?: boolean,
53
+ }
@@ -0,0 +1,50 @@
1
+ import { Flex, Text } from '@citric/core'
2
+ import { TimesMini } from '@citric/icons'
3
+ import { IconButton } from '@citric/ui'
4
+ import { WithStyle, listToClass, theme } from '@stack-spot/portal-theme'
5
+ import { ReactNode } from 'react'
6
+ import { styled } from 'styled-components'
7
+
8
+ export interface OverlayContentProps extends WithStyle {
9
+ title: string,
10
+ subtitle?: string,
11
+ children: ReactNode,
12
+ onClose?: () => void,
13
+ }
14
+
15
+ interface Props extends OverlayContentProps {
16
+ onClose: () => void,
17
+ type: 'modal' | 'panel',
18
+ }
19
+
20
+ const ContentBox = styled.section`
21
+ display: flex;
22
+ flex-direction: column;
23
+ border-radius: 1rem;
24
+ background-color: ${theme.color.light['400']};
25
+ &.modal {
26
+ padding: 32px;
27
+ }
28
+ &.panel {
29
+ padding: 20px;
30
+ }
31
+ header {
32
+ display: flex;
33
+ flex-direction: row;
34
+ flex: 1;
35
+ margin-bottom: 1.25rem;
36
+ }
37
+ `
38
+
39
+ export const OverlayContent = ({ children, title, subtitle, className, style, onClose, type }: Props) => (
40
+ <ContentBox style={style} className={listToClass([className, type])}>
41
+ <header>
42
+ <Flex flexDirection="column" flex={1}>
43
+ <Text appearance={type === 'modal' ? 'h3' : 'h4'}>{title}</Text>
44
+ {subtitle && <Text appearance="body2" colorScheme="light.700">{subtitle}</Text>}
45
+ </Flex>
46
+ <IconButton onClick={onClose} title="close" aria-label="close"><TimesMini /></IconButton>
47
+ </header>
48
+ {children}
49
+ </ContentBox>
50
+ )
@@ -0,0 +1,200 @@
1
+ import { Flex, IconBox, Text } from '@citric/core'
2
+ import { ArrowLeft, Check, ChevronRight } from '@citric/icons'
3
+ import { IconButton } from '@citric/ui'
4
+ import { WithStyle, listToClass, theme } from '@stack-spot/portal-theme'
5
+ import { ReactElement, useCallback, useEffect, useMemo, useRef, useState } from 'react'
6
+ import { styled } from 'styled-components'
7
+ import { Action } from './types'
8
+
9
+ interface ItemWithIcon {
10
+ icon?: React.ReactElement,
11
+ iconRight?: React.ReactElement,
12
+ }
13
+
14
+ export interface ListAction extends ItemWithIcon, Action {
15
+ active?: boolean,
16
+ }
17
+
18
+ interface ListGroup {
19
+ type?: 'section' | 'collapsible',
20
+ children: ListItem[],
21
+ }
22
+
23
+ interface ListSection extends ListGroup {
24
+ type: 'section',
25
+ label?: string,
26
+ }
27
+
28
+ interface ListCollapsible extends ListGroup, ItemWithIcon {
29
+ type?: 'collapsible',
30
+ label: string,
31
+ }
32
+
33
+ type ListItem = ListSection | ListCollapsible | ListAction
34
+
35
+ interface CurrentItemList {
36
+ items: ListItem[],
37
+ label?: string,
38
+ parent?: CurrentItemList,
39
+ }
40
+
41
+ const ANIMATION_DURATION_MS = 300
42
+
43
+ export interface SelectionListProps extends WithStyle {
44
+ visible?: boolean,
45
+ items: ListItem[],
46
+ onHide?: () => void,
47
+ maxHeight?: string,
48
+ before?: ReactElement,
49
+ after?: ReactElement,
50
+ }
51
+
52
+ const SelectionBox = styled.div<{ $maxHeight: string }>`
53
+ max-height: 0;
54
+ overflow: hidden;
55
+ transition: max-height ease-in ${ANIMATION_DURATION_MS / 1000}s;
56
+ z-index: 1;
57
+ box-shadow: 4px 4px 48px #000;
58
+ border-radius: 0.5rem;
59
+
60
+ .selection-list-content {
61
+ display: flex;
62
+ flex-direction: column;
63
+ border-radius: 0.5rem;
64
+ background: ${theme.color.light['500']};
65
+ border-radius: 0.5rem;
66
+ border: 1px solid ${theme.color.light['600']};
67
+ background-color: ${theme.color.light['300']};
68
+ }
69
+
70
+ .section-title, li > a {
71
+ height: 40px;
72
+ padding: 0 8px;
73
+ display: flex;
74
+ flex-direction: row;
75
+ align-items: center;
76
+ }
77
+
78
+ li > a {
79
+ gap: 4px;
80
+ transition: background-color 0.2s;
81
+ &:hover {
82
+ background: ${theme.color.light['400']};
83
+ }
84
+ .label {
85
+ flex: 1;
86
+ }
87
+ }
88
+
89
+ li.section {
90
+ border-bottom: 2px solid ${theme.color.light['600']};
91
+ &:last-child {
92
+ border-bottom: none;
93
+ }
94
+ }
95
+
96
+ &.visible {
97
+ max-height: ${({ $maxHeight }) => $maxHeight};
98
+ }
99
+ `
100
+
101
+ function renderAction({ label, href, onClick, icon, iconRight, active }: ListAction) {
102
+ return (
103
+ <li key={label} className="action">
104
+ <a href={href} onClick={onClick}>
105
+ {icon && <IconBox>{icon}</IconBox>}
106
+ <Text appearance="body2" className="label">{label}</Text>
107
+ {iconRight && <IconBox>{iconRight}</IconBox>}
108
+ {active && <IconBox><Check /></IconBox>}
109
+ </a>
110
+ </li>
111
+ )
112
+ }
113
+
114
+ function renderCollapsible({ label, icon, iconRight, children }: ListCollapsible, setCurrent: (current: CurrentItemList) => void) {
115
+ return (
116
+ <li key={label} className="collapsible">
117
+ <a onClick={() => setCurrent({ items: children, label })}>
118
+ {icon && <IconBox>{icon}</IconBox>}
119
+ <Text appearance="body2" className="label">{label}</Text>
120
+ {iconRight && <IconBox>{iconRight}</IconBox>}
121
+ <IconBox><ChevronRight /></IconBox>
122
+ </a>
123
+ </li>
124
+ )
125
+ }
126
+
127
+ function renderSection({ label, children }: ListSection, setCurrent: (current: CurrentItemList) => void) {
128
+ return (
129
+ <li key={label ?? children.map(c => c.label).join('-')} className="section">
130
+ {label && <Text appearance="overheader2" colorScheme="primary" className="section-title">{label}</Text>}
131
+ <ul>{children.map(i => renderItem(i, setCurrent))}</ul>
132
+ </li>
133
+ )
134
+ }
135
+
136
+ function renderItem(item: ListItem, setCurrent: (current: CurrentItemList) => void) {
137
+ if ('children' in item) {
138
+ return item.type === 'section' ? renderSection(item, setCurrent) : renderCollapsible(item, setCurrent)
139
+ }
140
+ return renderAction(item)
141
+ }
142
+
143
+ export const SelectionList = ({
144
+ items, className, style, visible = true, maxHeight = '300px', onHide, before, after,
145
+ }: SelectionListProps) => {
146
+ const wrapper = useRef<HTMLDivElement>(null)
147
+ const itemsRef = useRef(items)
148
+ const [current, setCurrent] = useState<CurrentItemList>({ items })
149
+ const listItems = useMemo(
150
+ () => current.items.map(i => renderItem(i, (next: CurrentItemList) => setCurrent({ ...next, parent: current }))),
151
+ [current],
152
+ )
153
+ const hide = useCallback((event: MouseEvent) => {
154
+ const target = (event.target as HTMLElement | null)
155
+ // if the element is not in the DOM anymore, we'll consider the click was inside the selection list
156
+ const isClickInsideSelectionList = !target?.isConnected || wrapper.current?.contains(target)
157
+ const isAction = target?.classList?.contains('action') || !!target?.closest('.action')
158
+ if (!isClickInsideSelectionList || isAction) {
159
+ if (onHide) onHide()
160
+ setTimeout(() => setCurrent({ items: itemsRef.current }), ANIMATION_DURATION_MS)
161
+ document.removeEventListener('click', hide)
162
+ }
163
+ }, [])
164
+
165
+ useEffect(() => {
166
+ if (!onHide) return
167
+ if (visible) setTimeout(() => document.addEventListener('click', hide), 50)
168
+ }, [visible])
169
+
170
+ useEffect(() => {
171
+ itemsRef.current = items
172
+ if (!wrapper.current?.classList.contains('visible')) setCurrent({ items })
173
+ }, [items])
174
+
175
+ return (
176
+ <SelectionBox
177
+ ref={wrapper}
178
+ $maxHeight={maxHeight}
179
+ style={style}
180
+ className={listToClass(['selection-list', visible ? 'visible' : undefined, className])}
181
+ >
182
+ <div className="selection-list-content">
183
+ {before}
184
+ {current.parent
185
+ ? (
186
+ <Flex mt={5} mb={1} alignItems="center">
187
+ <IconButton onClick={() => setCurrent(current.parent ?? { items })} sx={{ mr: 3 }}>
188
+ <ArrowLeft />
189
+ </IconButton>
190
+ <Text appearance="microtext1">{current.label}</Text>
191
+ </Flex>
192
+ )
193
+ : undefined
194
+ }
195
+ <ul>{listItems}</ul>
196
+ {after}
197
+ </div>
198
+ </SelectionBox>
199
+ )
200
+ }
@@ -0,0 +1,12 @@
1
+ import { TimesMini } from '@citric/icons'
2
+ import { IconButton } from '@citric/ui'
3
+ import { CloseButtonProps, ToastContainer } from 'react-toastify'
4
+ import 'react-toastify/dist/ReactToastify.css'
5
+
6
+ const CloseButton = ({ closeToast }: CloseButtonProps) => (
7
+ <IconButton onClick={() => closeToast(null as any)} title="Dismiss">
8
+ <TimesMini />
9
+ </IconButton>
10
+ )
11
+
12
+ export const Toaster = () => <ToastContainer closeButton={CloseButton} />
@@ -0,0 +1,91 @@
1
+ import { Flex, IconBox, LinkBox, Text } from '@citric/core'
2
+ import { ChevronDown } from '@citric/icons'
3
+ import { Avatar } from '@citric/ui'
4
+ import { theme } from '@stack-spot/portal-theme'
5
+ import { useState } from 'react'
6
+ import { styled } from 'styled-components'
7
+ import { SelectionList, SelectionListProps } from './SelectionList'
8
+
9
+ interface Props {
10
+ userName: string,
11
+ email?: string,
12
+ options?: SelectionListProps['items'],
13
+ }
14
+
15
+ const UserMenuBox = styled.div`
16
+ .user-menu-header {
17
+ display: flex;
18
+ flex-direction: column;
19
+ justify-content: center;
20
+ align-items: center;
21
+ padding: 12px;
22
+ border-bottom: 2px solid ${theme.color.light['600']};
23
+ }
24
+
25
+ .selection-list {
26
+ position: absolute;
27
+ top: var(--header-height);
28
+ right: 20px;
29
+ width: 266px;
30
+
31
+ .selection-list-content {
32
+ border: none;
33
+ padding: 16px 16px 8px;
34
+ background-color: ${theme.color.light['400']};
35
+ }
36
+
37
+ li {
38
+ margin: 8px 0;
39
+ & > a {
40
+ border-radius: 6px;
41
+ &:hover {
42
+ background: ${theme.color.light['500']};
43
+ }
44
+ }
45
+ }
46
+ }
47
+
48
+ .username {
49
+ margin: 5px 0 2px 0;
50
+ }
51
+
52
+ .chevron {
53
+ transition: transform ease-out 0.3s;
54
+ }
55
+ `
56
+
57
+ const UserMenuHeader = ({ userName, email }: Omit<Props, 'options'>) => (
58
+ <div className="user-menu-header">
59
+ <Avatar size="xs">{userName}</Avatar>
60
+ <Text appearance="body1" className="username">{userName}</Text>
61
+ {email && <Text appearance="microtext1" className="email" colorScheme="light.700">{email}</Text>}
62
+ </div>
63
+ )
64
+
65
+ export const UserMenu = ({ userName, email, options }: Props) => {
66
+ const [visible, setVisible] = useState(false)
67
+
68
+ return (
69
+ <UserMenuBox>
70
+ <LinkBox as="button" onClick={() => setVisible(true)}>
71
+ <Flex alignItems="center">
72
+ <Avatar size="xs">{userName}</Avatar>
73
+ <IconBox colorScheme="inverse" className="chevron" style={visible ? { transform: 'rotate(180deg)' } : undefined}>
74
+ <ChevronDown />
75
+ </IconBox>
76
+ </Flex>
77
+ </LinkBox>
78
+
79
+ {options?.length
80
+ ? <SelectionList
81
+ visible={visible}
82
+ before={<UserMenuHeader userName={userName} email={email} />}
83
+ items={options!}
84
+ onHide={() => setVisible(false)}
85
+ maxHeight="600px"
86
+ />
87
+ : null
88
+ }
89
+ </UserMenuBox>
90
+ )
91
+ }
@@ -0,0 +1,5 @@
1
+ export interface Action {
2
+ label: string,
3
+ onClick?: () => void,
4
+ href?: string,
5
+ }
package/src/errors.ts ADDED
@@ -0,0 +1,11 @@
1
+ export class LayoutError extends Error {
2
+ constructor(message: string) {
3
+ super(`Layout error: ${message}`)
4
+ }
5
+ }
6
+
7
+ export class ElementNotFound extends LayoutError {
8
+ constructor(elementName: string, elementId: string) {
9
+ super(`unable to create ${elementName} because no element with id "${elementId}" was found in the view.`)
10
+ }
11
+ }
package/src/index.ts ADDED
@@ -0,0 +1,10 @@
1
+ export { Layout } from './Layout'
2
+ export { overlay } from './LayoutOverlayManager'
3
+ export { Header, HeaderProps } from './components/Header'
4
+ export { StackspotLogo } from './components/Logo'
5
+ export { MenuContent } from './components/Menu/MenuContent'
6
+ export { MenuSections } from './components/Menu/MenuSections'
7
+ export * from './components/Menu/types'
8
+ export { ListAction, SelectionList, SelectionListProps } from './components/SelectionList'
9
+ export * from './components/types'
10
+ export * from './errors'