@stack-spot/portal-layout 0.0.2 → 0.0.4
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.
- package/dist/Layout.d.ts +19 -5
- package/dist/Layout.d.ts.map +1 -1
- package/dist/Layout.js +13 -3
- package/dist/Layout.js.map +1 -1
- package/dist/LayoutOverlayManager.d.ts +22 -7
- package/dist/LayoutOverlayManager.d.ts.map +1 -1
- package/dist/LayoutOverlayManager.js +36 -26
- package/dist/LayoutOverlayManager.js.map +1 -1
- package/dist/components/Dialog.d.ts +9 -1
- package/dist/components/Dialog.d.ts.map +1 -1
- package/dist/components/Dialog.js +12 -4
- package/dist/components/Dialog.js.map +1 -1
- package/dist/components/Header.d.ts +2 -1
- package/dist/components/Header.d.ts.map +1 -1
- package/dist/components/Header.js +1 -1
- package/dist/components/Header.js.map +1 -1
- package/dist/components/Menu/MenuContent.d.ts +1 -1
- package/dist/components/Menu/MenuContent.d.ts.map +1 -1
- package/dist/components/Menu/MenuContent.js +46 -12
- package/dist/components/Menu/MenuContent.js.map +1 -1
- package/dist/components/Menu/MenuSections.d.ts +2 -1
- package/dist/components/Menu/MenuSections.d.ts.map +1 -1
- package/dist/components/Menu/MenuSections.js +39 -9
- package/dist/components/Menu/MenuSections.js.map +1 -1
- package/dist/components/Menu/PageSelector.d.ts +1 -1
- package/dist/components/Menu/PageSelector.d.ts.map +1 -1
- package/dist/components/Menu/PageSelector.js +9 -3
- package/dist/components/Menu/PageSelector.js.map +1 -1
- package/dist/components/Menu/types.d.ts +47 -7
- package/dist/components/Menu/types.d.ts.map +1 -1
- package/dist/components/OverlayContent.d.ts.map +1 -1
- package/dist/components/OverlayContent.js +8 -2
- package/dist/components/OverlayContent.js.map +1 -1
- package/dist/components/SelectionList.d.ts +2 -1
- package/dist/components/SelectionList.d.ts.map +1 -1
- package/dist/components/SelectionList.js +7 -3
- package/dist/components/SelectionList.js.map +1 -1
- package/dist/components/Toaster.d.ts.map +1 -1
- package/dist/components/Toaster.js +5 -1
- package/dist/components/Toaster.js.map +1 -1
- package/dist/dictionary.d.ts +15 -0
- package/dist/dictionary.d.ts.map +1 -0
- package/dist/dictionary.js +23 -0
- package/dist/dictionary.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/layout.css +38 -10
- package/package.json +4 -3
- package/src/Layout.tsx +46 -16
- package/src/LayoutOverlayManager.tsx +57 -29
- package/src/components/Dialog.tsx +38 -7
- package/src/components/Header.tsx +3 -2
- package/src/components/Menu/MenuContent.tsx +60 -16
- package/src/components/Menu/MenuSections.tsx +58 -14
- package/src/components/Menu/PageSelector.tsx +25 -12
- package/src/components/Menu/types.ts +50 -7
- package/src/components/OverlayContent.tsx +19 -13
- package/src/components/SelectionList.tsx +9 -3
- package/src/components/Toaster.tsx +9 -5
- package/src/dictionary.ts +25 -0
- package/src/index.ts +2 -0
- package/src/layout.css +38 -10
- package/src/citric.fix.d.ts +0 -7
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { IconBox, Text } from '@citric/core'
|
|
2
2
|
import { ArrowRight, Select } from '@citric/icons'
|
|
3
|
+
import { LoadingCircular } from '@citric/ui'
|
|
3
4
|
import { theme } from '@stack-spot/portal-theme'
|
|
4
5
|
import { useMemo, useState } from 'react'
|
|
5
6
|
import { styled } from 'styled-components'
|
|
@@ -27,6 +28,9 @@ const SelectorBox = styled.div`
|
|
|
27
28
|
|
|
28
29
|
.label {
|
|
29
30
|
flex: 1;
|
|
31
|
+
white-space: nowrap;
|
|
32
|
+
overflow: hidden;
|
|
33
|
+
text-overflow: ellipsis;
|
|
30
34
|
}
|
|
31
35
|
}
|
|
32
36
|
|
|
@@ -71,7 +75,7 @@ const SelectorBox = styled.div`
|
|
|
71
75
|
}
|
|
72
76
|
`
|
|
73
77
|
|
|
74
|
-
export const PageSelector = ({ options, value, button }: Selector) => {
|
|
78
|
+
export const PageSelector = ({ options, value, button, loading, title }: Selector) => {
|
|
75
79
|
const [visible, setVisible] = useState(false)
|
|
76
80
|
const { optionsWithIcon, selected } = useMemo(
|
|
77
81
|
() => {
|
|
@@ -91,18 +95,27 @@ export const PageSelector = ({ options, value, button }: Selector) => {
|
|
|
91
95
|
|
|
92
96
|
return (
|
|
93
97
|
<SelectorBox>
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
98
|
+
{loading
|
|
99
|
+
? <LoadingCircular />
|
|
100
|
+
: (
|
|
101
|
+
<>
|
|
102
|
+
{title && <Text colorScheme="light.700" sx={{ mb: 3 }}>{title}</Text>}
|
|
103
|
+
<a onClick={() => setVisible(true)} aria-label={value}>
|
|
104
|
+
{selected?.icon && <IconBox>{selected?.icon}</IconBox>}
|
|
105
|
+
<Text appearance="body2" className="label">{selected?.label ?? button?.label ?? value}</Text>
|
|
106
|
+
<IconBox size="xs"><Select /></IconBox>
|
|
107
|
+
</a>
|
|
99
108
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
109
|
+
<SelectionList
|
|
110
|
+
visible={visible}
|
|
111
|
+
items={optionsWithIcon}
|
|
112
|
+
onHide={() => setVisible(false)}
|
|
113
|
+
after={button ? <a className="view-all" href={button.href} onClick={button.onClick}>{button.label}</a> : undefined}
|
|
114
|
+
scroll
|
|
115
|
+
/>
|
|
116
|
+
</>
|
|
117
|
+
)
|
|
118
|
+
}
|
|
106
119
|
</SelectorBox>
|
|
107
120
|
)
|
|
108
121
|
}
|
|
@@ -1,16 +1,31 @@
|
|
|
1
1
|
import { ReactElement } from 'react'
|
|
2
2
|
import { Action } from '../types'
|
|
3
3
|
|
|
4
|
-
|
|
4
|
+
interface BaseMenuItem {
|
|
5
|
+
hidden?: boolean,
|
|
6
|
+
/**
|
|
7
|
+
* React element on the left.
|
|
8
|
+
*/
|
|
9
|
+
icon?: React.ReactElement,
|
|
10
|
+
/**
|
|
11
|
+
* React element on the right.
|
|
12
|
+
*/
|
|
13
|
+
badge?: React.ReactElement,
|
|
14
|
+
/**
|
|
15
|
+
* Whether to wrap overflowing text, just hide or hide and add an ellipsis (...).
|
|
16
|
+
* @default 'wrap'
|
|
17
|
+
*/
|
|
18
|
+
overflow?: 'hidden' | 'wrap' | 'ellipsis',
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface ItemGroup extends BaseMenuItem {
|
|
5
22
|
label: string,
|
|
6
23
|
children: MenuItem[],
|
|
7
24
|
open?: boolean,
|
|
8
|
-
hidden?: boolean,
|
|
9
25
|
}
|
|
10
26
|
|
|
11
|
-
export interface MenuAction extends Action {
|
|
27
|
+
export interface MenuAction extends Action, BaseMenuItem {
|
|
12
28
|
active?: boolean,
|
|
13
|
-
hidden?: boolean,
|
|
14
29
|
}
|
|
15
30
|
|
|
16
31
|
export type MenuItem = ItemGroup | MenuAction
|
|
@@ -30,6 +45,7 @@ export interface Selector {
|
|
|
30
45
|
button?: Action,
|
|
31
46
|
title?: string,
|
|
32
47
|
subtitle?: string,
|
|
48
|
+
loading?: boolean,
|
|
33
49
|
}
|
|
34
50
|
|
|
35
51
|
export interface MenuSectionContent {
|
|
@@ -38,16 +54,43 @@ export interface MenuSectionContent {
|
|
|
38
54
|
subtitle?: string,
|
|
39
55
|
pageSelector?: Selector,
|
|
40
56
|
options?: MenuItem[],
|
|
57
|
+
loading?: boolean,
|
|
58
|
+
error?: string,
|
|
41
59
|
}
|
|
42
60
|
|
|
43
61
|
export interface MenuSection extends Action {
|
|
44
62
|
icon: ReactElement,
|
|
45
|
-
|
|
63
|
+
/**
|
|
64
|
+
* The content or a function that creates the content.
|
|
65
|
+
* If this is a function, it will be called only when the section is hovered, i.e. only when the content really needs to be rendered.
|
|
66
|
+
* Tip: this function can be a React Hook.
|
|
67
|
+
*/
|
|
68
|
+
content?: MenuSectionContent | (() => MenuSectionContent),
|
|
46
69
|
active?: boolean,
|
|
70
|
+
onOpen?: () => void,
|
|
47
71
|
}
|
|
48
72
|
|
|
49
|
-
|
|
73
|
+
interface BaseMenuProps {
|
|
50
74
|
sections: MenuSection[],
|
|
51
|
-
content?: MenuSectionContent,
|
|
52
75
|
compact?: boolean,
|
|
53
76
|
}
|
|
77
|
+
|
|
78
|
+
interface MenuPropsWithStaticContent extends BaseMenuProps {
|
|
79
|
+
content?: MenuSectionContent,
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
interface MenuPropsWithDynamicContent extends BaseMenuProps {
|
|
83
|
+
/**
|
|
84
|
+
* The function that creates the content. It will be called only when the content is rendered, i.e. only when the content really needs to
|
|
85
|
+
* be rendered.
|
|
86
|
+
*
|
|
87
|
+
* Tip: this function can be a React Hook.
|
|
88
|
+
*/
|
|
89
|
+
content: MenuSectionContent | (() => MenuSectionContent),
|
|
90
|
+
/**
|
|
91
|
+
* Identifies each content that might be rendered by the menu. This prevents React Hook errors when the content is a React Hook function.
|
|
92
|
+
*/
|
|
93
|
+
contentKey: React.Key,
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export type MenuProps = MenuPropsWithStaticContent | MenuPropsWithDynamicContent
|
|
@@ -4,6 +4,7 @@ import { IconButton } from '@citric/ui'
|
|
|
4
4
|
import { WithStyle, listToClass, theme } from '@stack-spot/portal-theme'
|
|
5
5
|
import { ReactNode } from 'react'
|
|
6
6
|
import { styled } from 'styled-components'
|
|
7
|
+
import { useDictionary } from '../dictionary'
|
|
7
8
|
|
|
8
9
|
export interface OverlayContentProps extends WithStyle {
|
|
9
10
|
title: string,
|
|
@@ -27,24 +28,29 @@ const ContentBox = styled.section`
|
|
|
27
28
|
}
|
|
28
29
|
&.panel {
|
|
29
30
|
padding: 20px;
|
|
31
|
+
display: flex;
|
|
32
|
+
flex-direction: column;
|
|
33
|
+
flex: 1;
|
|
30
34
|
}
|
|
31
35
|
header {
|
|
32
36
|
display: flex;
|
|
33
37
|
flex-direction: row;
|
|
34
|
-
flex: 1;
|
|
35
38
|
margin-bottom: 1.25rem;
|
|
36
39
|
}
|
|
37
40
|
`
|
|
38
41
|
|
|
39
|
-
export const OverlayContent = ({ children, title, subtitle, className, style, onClose, type }: Props) =>
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
42
|
+
export const OverlayContent = ({ children, title, subtitle, className, style, onClose, type }: Props) => {
|
|
43
|
+
const t = useDictionary()
|
|
44
|
+
return (
|
|
45
|
+
<ContentBox style={style} className={listToClass([className, type])}>
|
|
46
|
+
<header>
|
|
47
|
+
<Flex flexDirection="column" flex={1}>
|
|
48
|
+
<Text appearance={type === 'modal' ? 'h3' : 'h4'}>{title}</Text>
|
|
49
|
+
{subtitle && <Text appearance="body2" colorScheme="light.700">{subtitle}</Text>}
|
|
50
|
+
</Flex>
|
|
51
|
+
<IconButton onClick={onClose} title={t.close} aria-label={t.close}><TimesMini /></IconButton>
|
|
52
|
+
</header>
|
|
53
|
+
{children}
|
|
54
|
+
</ContentBox>
|
|
55
|
+
)
|
|
56
|
+
}
|
|
@@ -47,11 +47,13 @@ export interface SelectionListProps extends WithStyle {
|
|
|
47
47
|
maxHeight?: string,
|
|
48
48
|
before?: ReactElement,
|
|
49
49
|
after?: ReactElement,
|
|
50
|
+
scroll?: boolean,
|
|
50
51
|
}
|
|
51
52
|
|
|
52
|
-
const SelectionBox = styled.div<{ $maxHeight: string }>`
|
|
53
|
+
const SelectionBox = styled.div<{ $maxHeight: string, $scroll?: boolean }>`
|
|
53
54
|
max-height: 0;
|
|
54
|
-
overflow: hidden;
|
|
55
|
+
overflow-y: ${({ $scroll }) => $scroll ? 'auto' : 'hidden'};
|
|
56
|
+
overflow-x: hidden;
|
|
55
57
|
transition: max-height ease-in ${ANIMATION_DURATION_MS / 1000}s;
|
|
56
58
|
z-index: 1;
|
|
57
59
|
box-shadow: 4px 4px 48px #000;
|
|
@@ -83,6 +85,9 @@ const SelectionBox = styled.div<{ $maxHeight: string }>`
|
|
|
83
85
|
}
|
|
84
86
|
.label {
|
|
85
87
|
flex: 1;
|
|
88
|
+
white-space: nowrap;
|
|
89
|
+
overflow: hidden;
|
|
90
|
+
text-overflow: ellipsis;
|
|
86
91
|
}
|
|
87
92
|
}
|
|
88
93
|
|
|
@@ -141,7 +146,7 @@ function renderItem(item: ListItem, setCurrent: (current: CurrentItemList) => vo
|
|
|
141
146
|
}
|
|
142
147
|
|
|
143
148
|
export const SelectionList = ({
|
|
144
|
-
items, className, style, visible = true, maxHeight = '300px', onHide, before, after,
|
|
149
|
+
items, className, style, visible = true, maxHeight = '300px', onHide, before, after, scroll,
|
|
145
150
|
}: SelectionListProps) => {
|
|
146
151
|
const wrapper = useRef<HTMLDivElement>(null)
|
|
147
152
|
const itemsRef = useRef(items)
|
|
@@ -178,6 +183,7 @@ export const SelectionList = ({
|
|
|
178
183
|
$maxHeight={maxHeight}
|
|
179
184
|
style={style}
|
|
180
185
|
className={listToClass(['selection-list', visible ? 'visible' : undefined, className])}
|
|
186
|
+
$scroll={scroll}
|
|
181
187
|
>
|
|
182
188
|
<div className="selection-list-content">
|
|
183
189
|
{before}
|
|
@@ -2,11 +2,15 @@ import { TimesMini } from '@citric/icons'
|
|
|
2
2
|
import { IconButton } from '@citric/ui'
|
|
3
3
|
import { CloseButtonProps, ToastContainer } from 'react-toastify'
|
|
4
4
|
import 'react-toastify/dist/ReactToastify.css'
|
|
5
|
+
import { useDictionary } from '../dictionary'
|
|
5
6
|
|
|
6
|
-
const CloseButton = ({ closeToast }: CloseButtonProps) =>
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
7
|
+
const CloseButton = ({ closeToast }: CloseButtonProps) => {
|
|
8
|
+
const t = useDictionary()
|
|
9
|
+
return (
|
|
10
|
+
<IconButton onClick={() => closeToast(null as any)} title={t.dismiss}>
|
|
11
|
+
<TimesMini />
|
|
12
|
+
</IconButton>
|
|
13
|
+
)
|
|
14
|
+
}
|
|
11
15
|
|
|
12
16
|
export const Toaster = () => <ToastContainer closeButton={CloseButton} />
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { Dictionary, getLanguage, useTranslate } from '@stack-spot/portal-translate'
|
|
2
|
+
|
|
3
|
+
const dictionary = {
|
|
4
|
+
en: {
|
|
5
|
+
close: 'Close',
|
|
6
|
+
validationLabel: 'Please, confirm the action by typing "$0" below:',
|
|
7
|
+
dismiss: 'Dismiss',
|
|
8
|
+
confirm: 'OK',
|
|
9
|
+
cancel: 'Cancel',
|
|
10
|
+
},
|
|
11
|
+
pt: {
|
|
12
|
+
close: 'Fechar',
|
|
13
|
+
validationLabel: 'Por favor, confirme a ação digitando "$0" no campo abaixo:',
|
|
14
|
+
dismiss: 'Dispensar',
|
|
15
|
+
confirm: 'OK',
|
|
16
|
+
cancel: 'Cancelar',
|
|
17
|
+
},
|
|
18
|
+
} satisfies Dictionary
|
|
19
|
+
|
|
20
|
+
export const useDictionary = () => useTranslate(dictionary)
|
|
21
|
+
|
|
22
|
+
export function getDictionary() {
|
|
23
|
+
const language = getLanguage()
|
|
24
|
+
return dictionary[language]
|
|
25
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
export { Layout } from './Layout'
|
|
2
2
|
export { overlay } from './LayoutOverlayManager'
|
|
3
|
+
export { Dialog } from './components/Dialog'
|
|
3
4
|
export { Header, HeaderProps } from './components/Header'
|
|
4
5
|
export { StackspotLogo } from './components/Logo'
|
|
5
6
|
export { MenuContent } from './components/Menu/MenuContent'
|
|
6
7
|
export { MenuSections } from './components/Menu/MenuSections'
|
|
7
8
|
export * from './components/Menu/types'
|
|
9
|
+
export { OverlayContent } from './components/OverlayContent'
|
|
8
10
|
export { ListAction, SelectionList, SelectionListProps } from './components/SelectionList'
|
|
9
11
|
export * from './components/types'
|
|
10
12
|
export * from './errors'
|
package/src/layout.css
CHANGED
|
@@ -68,20 +68,23 @@ body {
|
|
|
68
68
|
}
|
|
69
69
|
|
|
70
70
|
#page {
|
|
71
|
-
|
|
71
|
+
position: absolute;
|
|
72
|
+
top: var(--header-height);
|
|
73
|
+
left: var(--menu-sections-width);
|
|
74
|
+
right: 0;
|
|
75
|
+
bottom: 0;
|
|
76
|
+
overflow-y: auto;
|
|
72
77
|
display: flex;
|
|
73
78
|
flex-direction: column;
|
|
74
79
|
background-color: var(--light-300);
|
|
75
80
|
border-top-left-radius: 0.5rem;
|
|
76
81
|
align-items: center;
|
|
77
82
|
padding: 24px;
|
|
78
|
-
|
|
79
|
-
margin-left: var(--menu-sections-width);
|
|
80
|
-
transition: margin ease-in-out var(--menu-animation-duration);
|
|
83
|
+
transition: left ease-in-out var(--menu-animation-duration);
|
|
81
84
|
}
|
|
82
85
|
|
|
83
86
|
#layout.menu-content-visible #page {
|
|
84
|
-
|
|
87
|
+
left: calc(var(--menu-sections-width) + var(--menu-content-width));
|
|
85
88
|
}
|
|
86
89
|
|
|
87
90
|
#content {
|
|
@@ -298,11 +301,25 @@ body {
|
|
|
298
301
|
display: flex;
|
|
299
302
|
flex-direction: column;
|
|
300
303
|
top: var(--header-height);
|
|
301
|
-
right: -307px;
|
|
302
304
|
bottom: 0;
|
|
303
|
-
width: 307px;
|
|
304
305
|
transition: right var(--right-panel-animation-duration);
|
|
305
306
|
background-color: var(--light-400);
|
|
307
|
+
right: -800px;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
#rightPanel.small {
|
|
311
|
+
right: -400px;
|
|
312
|
+
width: 400px;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
#rightPanel.medium {
|
|
316
|
+
right: -600px;
|
|
317
|
+
width: 600px;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
#rightPanel.large {
|
|
321
|
+
right: -800px;
|
|
322
|
+
width: 800px;
|
|
306
323
|
}
|
|
307
324
|
|
|
308
325
|
#rightPanel.visible {
|
|
@@ -316,7 +333,7 @@ body {
|
|
|
316
333
|
bottom: -420px;
|
|
317
334
|
left: 20px;
|
|
318
335
|
height: 400px;
|
|
319
|
-
transition:
|
|
336
|
+
transition: bottom 0.3s;
|
|
320
337
|
}
|
|
321
338
|
|
|
322
339
|
#bottomPanel.visible {
|
|
@@ -326,12 +343,23 @@ body {
|
|
|
326
343
|
#bottomDialog {
|
|
327
344
|
position: fixed;
|
|
328
345
|
display: flex;
|
|
329
|
-
flex-direction:
|
|
346
|
+
flex-direction: row;
|
|
330
347
|
bottom: -80px;
|
|
331
348
|
left: 0;
|
|
332
349
|
right: 0;
|
|
333
350
|
height: 80px;
|
|
334
|
-
transition:
|
|
351
|
+
transition: bottom 0.3s;
|
|
352
|
+
background-color: var(--inverse-500);
|
|
353
|
+
color: var(--inverse-contrastText);
|
|
354
|
+
justify-content: center;
|
|
355
|
+
align-items: center;
|
|
356
|
+
gap: 16px;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
#bottomDialog .btn-group {
|
|
360
|
+
display: flex;
|
|
361
|
+
flex-direction: row;
|
|
362
|
+
gap: 8px;
|
|
335
363
|
}
|
|
336
364
|
|
|
337
365
|
#bottomDialog.visible {
|
package/src/citric.fix.d.ts
DELETED
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
import '../node_modules/styled-components/dist/index.d.ts'
|
|
2
|
-
|
|
3
|
-
declare module 'styled-components' {
|
|
4
|
-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
5
|
-
type StyledComponent<Tag = 'div', Theme = object, Props = object, Optional = never> =
|
|
6
|
-
import('react').FunctionComponent<JSX.IntrinsicElements[Tag] & Props>
|
|
7
|
-
}
|