@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.
Files changed (65) hide show
  1. package/dist/Layout.d.ts +19 -5
  2. package/dist/Layout.d.ts.map +1 -1
  3. package/dist/Layout.js +13 -3
  4. package/dist/Layout.js.map +1 -1
  5. package/dist/LayoutOverlayManager.d.ts +22 -7
  6. package/dist/LayoutOverlayManager.d.ts.map +1 -1
  7. package/dist/LayoutOverlayManager.js +36 -26
  8. package/dist/LayoutOverlayManager.js.map +1 -1
  9. package/dist/components/Dialog.d.ts +9 -1
  10. package/dist/components/Dialog.d.ts.map +1 -1
  11. package/dist/components/Dialog.js +12 -4
  12. package/dist/components/Dialog.js.map +1 -1
  13. package/dist/components/Header.d.ts +2 -1
  14. package/dist/components/Header.d.ts.map +1 -1
  15. package/dist/components/Header.js +1 -1
  16. package/dist/components/Header.js.map +1 -1
  17. package/dist/components/Menu/MenuContent.d.ts +1 -1
  18. package/dist/components/Menu/MenuContent.d.ts.map +1 -1
  19. package/dist/components/Menu/MenuContent.js +46 -12
  20. package/dist/components/Menu/MenuContent.js.map +1 -1
  21. package/dist/components/Menu/MenuSections.d.ts +2 -1
  22. package/dist/components/Menu/MenuSections.d.ts.map +1 -1
  23. package/dist/components/Menu/MenuSections.js +39 -9
  24. package/dist/components/Menu/MenuSections.js.map +1 -1
  25. package/dist/components/Menu/PageSelector.d.ts +1 -1
  26. package/dist/components/Menu/PageSelector.d.ts.map +1 -1
  27. package/dist/components/Menu/PageSelector.js +9 -3
  28. package/dist/components/Menu/PageSelector.js.map +1 -1
  29. package/dist/components/Menu/types.d.ts +47 -7
  30. package/dist/components/Menu/types.d.ts.map +1 -1
  31. package/dist/components/OverlayContent.d.ts.map +1 -1
  32. package/dist/components/OverlayContent.js +8 -2
  33. package/dist/components/OverlayContent.js.map +1 -1
  34. package/dist/components/SelectionList.d.ts +2 -1
  35. package/dist/components/SelectionList.d.ts.map +1 -1
  36. package/dist/components/SelectionList.js +7 -3
  37. package/dist/components/SelectionList.js.map +1 -1
  38. package/dist/components/Toaster.d.ts.map +1 -1
  39. package/dist/components/Toaster.js +5 -1
  40. package/dist/components/Toaster.js.map +1 -1
  41. package/dist/dictionary.d.ts +15 -0
  42. package/dist/dictionary.d.ts.map +1 -0
  43. package/dist/dictionary.js +23 -0
  44. package/dist/dictionary.js.map +1 -0
  45. package/dist/index.d.ts +2 -0
  46. package/dist/index.d.ts.map +1 -1
  47. package/dist/index.js +2 -0
  48. package/dist/index.js.map +1 -1
  49. package/dist/layout.css +38 -10
  50. package/package.json +4 -3
  51. package/src/Layout.tsx +46 -16
  52. package/src/LayoutOverlayManager.tsx +57 -29
  53. package/src/components/Dialog.tsx +38 -7
  54. package/src/components/Header.tsx +3 -2
  55. package/src/components/Menu/MenuContent.tsx +60 -16
  56. package/src/components/Menu/MenuSections.tsx +58 -14
  57. package/src/components/Menu/PageSelector.tsx +25 -12
  58. package/src/components/Menu/types.ts +50 -7
  59. package/src/components/OverlayContent.tsx +19 -13
  60. package/src/components/SelectionList.tsx +9 -3
  61. package/src/components/Toaster.tsx +9 -5
  62. package/src/dictionary.ts +25 -0
  63. package/src/index.ts +2 -0
  64. package/src/layout.css +38 -10
  65. 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
- <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>
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
- <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
- />
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
- export interface ItemGroup {
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
- content?: MenuSectionContent,
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
- export interface MenuProps {
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
- <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
- )
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
- <IconButton onClick={() => closeToast(null as any)} title="Dismiss">
8
- <TimesMini />
9
- </IconButton>
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
- flex: 1;
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
- margin-top: var(--header-height);
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
- margin-left: calc(var(--menu-sections-width) + var(--menu-content-width));
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: transform bottom 0.3s;
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: column;
346
+ flex-direction: row;
330
347
  bottom: -80px;
331
348
  left: 0;
332
349
  right: 0;
333
350
  height: 80px;
334
- transition: transform bottom 0.3s;
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 {
@@ -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
- }