@stack-spot/portal-layout 0.0.26 → 0.0.28

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 (40) hide show
  1. package/dist/components/PortalSwitcher.d.ts.map +1 -1
  2. package/dist/components/PortalSwitcher.js +77 -49
  3. package/dist/components/PortalSwitcher.js.map +1 -1
  4. package/dist/components/SelectionList.d.ts.map +1 -1
  5. package/dist/components/SelectionList.js +5 -58
  6. package/dist/components/SelectionList.js.map +1 -1
  7. package/dist/components/menu/MenuContent.d.ts.map +1 -1
  8. package/dist/components/menu/MenuContent.js +17 -5
  9. package/dist/components/menu/MenuContent.js.map +1 -1
  10. package/dist/components/menu/MenuSections.d.ts.map +1 -1
  11. package/dist/components/menu/MenuSections.js +37 -9
  12. package/dist/components/menu/MenuSections.js.map +1 -1
  13. package/dist/components/menu/PageSelector.d.ts.map +1 -1
  14. package/dist/components/menu/PageSelector.js +7 -2
  15. package/dist/components/menu/PageSelector.js.map +1 -1
  16. package/dist/components/menu/types.d.ts +8 -0
  17. package/dist/components/menu/types.d.ts.map +1 -1
  18. package/dist/components/menu/use-check-text-overflow.d.ts +6 -0
  19. package/dist/components/menu/use-check-text-overflow.d.ts.map +1 -0
  20. package/dist/components/menu/use-check-text-overflow.js +20 -0
  21. package/dist/components/menu/use-check-text-overflow.js.map +1 -0
  22. package/dist/components/menu/use-keyboard-controls.d.ts +10 -0
  23. package/dist/components/menu/use-keyboard-controls.d.ts.map +1 -0
  24. package/dist/components/menu/use-keyboard-controls.js +74 -0
  25. package/dist/components/menu/use-keyboard-controls.js.map +1 -0
  26. package/dist/components/menu/useCheckTextOverflow.js.map +1 -1
  27. package/dist/components/types.d.ts +7 -2
  28. package/dist/components/types.d.ts.map +1 -1
  29. package/dist/layout.css +27 -6
  30. package/package.json +5 -5
  31. package/src/components/PortalSwitcher.tsx +84 -69
  32. package/src/components/SelectionList.tsx +7 -62
  33. package/src/components/menu/MenuContent.tsx +18 -5
  34. package/src/components/menu/MenuSections.tsx +99 -35
  35. package/src/components/menu/PageSelector.tsx +10 -5
  36. package/src/components/menu/types.ts +8 -0
  37. package/src/components/menu/{useCheckTextOverflow.tsx → use-check-text-overflow.tsx} +2 -2
  38. package/src/components/menu/use-keyboard-controls.tsx +88 -0
  39. package/src/components/types.ts +6 -2
  40. package/src/layout.css +27 -6
@@ -9,7 +9,7 @@ import { hideOverlayImmediately } from './MenuSections'
9
9
  import { PageSelector } from './PageSelector'
10
10
  import { MENU_CONTENT_ITEM_PADDING as ITEM_PADDING, MENU_CONTENT_PADDING as PADDING } from './constants'
11
11
  import { ItemGroup, MenuAction, MenuItem, MenuSectionContent } from './types'
12
- import { useCheckTextOverflow } from './useCheckTextOverflow'
12
+ import { useCheckTextOverflow } from './use-check-text-overflow'
13
13
 
14
14
  const BackLink = styled.a`
15
15
  display: flex;
@@ -69,7 +69,7 @@ export const MenuGroup = styled.ul`
69
69
  left: 2px;
70
70
  width: 2px;
71
71
  height: 0;
72
- background: ${theme.color.primary['500']};
72
+ background: inherit;
73
73
  border-radius: 50%;
74
74
  transition: height 0.2s;
75
75
  }
@@ -82,9 +82,15 @@ export const MenuGroup = styled.ul`
82
82
  }
83
83
 
84
84
  &:before {
85
+ background: ${theme.color.primary['500']};
85
86
  height: 24px;
86
87
  }
87
88
  }
89
+
90
+ &:not(.active):hover:before {
91
+ background: ${theme.color.light.contrastText};
92
+ height: 24px;
93
+ }
88
94
  }
89
95
 
90
96
  .chevron {
@@ -135,6 +141,7 @@ export const Title = styled.header`
135
141
 
136
142
  export const ActionItem = ({ label, onClick, href, active, icon, badge, overflow = 'wrap' }: MenuAction) => {
137
143
  const { ref, overflow: textOverflow } = useCheckTextOverflow()
144
+ const isTextLabel = typeof label === 'string'
138
145
  return (
139
146
  <a
140
147
  href={active ? undefined : href}
@@ -145,9 +152,12 @@ export const ActionItem = ({ label, onClick, href, active, icon, badge, overflow
145
152
  }}
146
153
  className={listToClass(['action', 'item-row', active ? 'active' : undefined])}
147
154
  {...(active ? { 'aria-current': 'page' } : undefined)}
155
+ {...(!href ? { 'tabIndex': 0 } : undefined)}
148
156
  >
149
157
  {icon}
150
- <Text ref={ref} appearance="body2" className={`label ${overflow}`} title={textOverflow ? label : ''}>{label}</Text>
158
+ {isTextLabel ?
159
+ <Text ref={ref} appearance="body2" className={`label ${overflow}`} title={textOverflow ? label : ''}>{label}</Text> :
160
+ label.element}
151
161
  {badge}
152
162
  </a>
153
163
  )
@@ -198,7 +208,8 @@ const GroupItem = ({ root, ...item }: ItemGroup & { root?: boolean }) => (
198
208
  )
199
209
 
200
210
  function renderOption({ root, ...option }: MenuItem & { root?: boolean }) {
201
- return <li key={option.label}>{'children' in option ? <GroupItem root={root} {...option} /> : <ActionItem {...option} />}</li>
211
+ const labelText = typeof option.label === 'string' ? option.label : option.label.id
212
+ return <li key={labelText}>{'children' in option ? <GroupItem root={root} {...option} /> : <ActionItem {...option} />}</li>
202
213
  }
203
214
 
204
215
  export const MenuContent = ({ pageSelector, goBack, title, subtitle, afterTitle, options = [], loading, error }: MenuSectionContent) => {
@@ -223,7 +234,9 @@ export const MenuContent = ({ pageSelector, goBack, title, subtitle, afterTitle,
223
234
  <IconBox colorScheme="inverse" size="sm">
224
235
  <ArrowLeft />
225
236
  </IconBox>
226
- <Text appearance="body2" nowrapEllipsis>{goBack.label}</Text>
237
+ {typeof goBack?.label === 'string' ?
238
+ <Text appearance="body2" nowrapEllipsis>{goBack.label}</Text> :
239
+ goBack.label.element}
227
240
  </BackLink>
228
241
  )}
229
242
  {title && (
@@ -1,10 +1,10 @@
1
- /* eslint-disable @typescript-eslint/no-unused-vars */
2
- import { IconBox, Text } from '@citric/core'
3
- import { ChevronLeft, Menu as MenuIcon } from '@citric/icons'
4
- import { Dictionary, useTranslate } from '@stack-spot/portal-translate'
1
+ import { Flex, IconBox, Text } from '@citric/core'
2
+ import { ChevronRight, Cog, Collapse, Menu as MenuIcon } from '@citric/icons'
3
+ import { Dictionary, interpolate, useTranslate } from '@stack-spot/portal-translate'
5
4
  import { useCallback, useMemo, useState } from 'react'
6
5
  import { MenuContent } from './MenuContent'
7
6
  import { MenuProps, MenuSection } from './types'
7
+ import { useKeyboardControls } from './use-keyboard-controls'
8
8
 
9
9
  const ARROW_HEIGHT = 24
10
10
  const HIDE_OVERLAY_DELAY_MS = 400
@@ -50,9 +50,12 @@ const Section = ({
50
50
  setCurrentOverlay,
51
51
  id,
52
52
  hasContent,
53
- }: MenuSection & { id: number, setCurrentOverlay: (id: number | undefined) => void, hasContent: boolean }) => {
53
+ className,
54
+ }: MenuSection & {
55
+ id: number, setCurrentOverlay: (id: number | undefined) => void, hasContent: boolean,
56
+ }) => {
54
57
  const contentToRender = typeof content === 'function' ? content() : content
55
-
58
+ const t = useTranslate(dictionary)
56
59
  function shouldShowOverlay() {
57
60
  /* The overlay should appear if:
58
61
  * 1. The section has some content to render OR:
@@ -63,7 +66,7 @@ const Section = ({
63
66
  return (!!contentToRender || !!customContent || (hasContent && active)) && (!active || !isMenuContentVisible())
64
67
  }
65
68
 
66
- function showOverlayAndFixArrowPosition(event: React.MouseEvent<HTMLAnchorElement, MouseEvent>) {
69
+ function showOverlayAndFixArrowPosition(event: React.MouseEvent<HTMLAnchorElement, MouseEvent> | React.KeyboardEvent<any>) {
67
70
  if (!shouldShowOverlay()) return
68
71
  onOpen?.()
69
72
  const rect = (event.target as HTMLElement)?.getBoundingClientRect()
@@ -80,23 +83,45 @@ const Section = ({
80
83
  hideOverlayImmediately()
81
84
  }
82
85
 
86
+ const labelText = typeof label === 'string' ? label : label.id
87
+
83
88
  return (
84
- <li key={label} title={label} className={active ? 'active' : undefined}>
85
- <a
86
- href={href}
87
- target={target}
88
- onClick={click}
89
- onMouseEnter={showOverlayAndFixArrowPosition}
90
- onMouseLeave={() => shouldShowOverlay() && hideOverlay()}
91
- title={label}
92
- aria-label={label}
93
- onKeyDown={onClick ? e => e.key === 'Enter' && onClick() : undefined}
94
- {...(active ? { 'aria-current': 'page' } : undefined)}
95
- >
96
- {icon}
97
- <Text appearance="microtext1" className="section-label">{label}</Text>
98
- </a>
99
- </li>
89
+ <>
90
+ <li key={labelText} title={labelText} className={`section-submenu ${className || ''} ${active ? 'active' : ''}`}>
91
+ <a
92
+ href={href}
93
+ target={target}
94
+ onClick={click}
95
+ onMouseEnter={showOverlayAndFixArrowPosition}
96
+ onMouseLeave={() => shouldShowOverlay() && hideOverlay()}
97
+ title={labelText}
98
+ aria-label={labelText}
99
+ onKeyDown={onClick ? e => e.key === 'Enter' && onClick() : undefined}
100
+ {...(active ? { 'aria-current': 'page' } : undefined)}
101
+ {...(!href ? { 'tabIndex': 0 } : undefined)}
102
+ >
103
+ <Flex alignItems="center" justifyContent="center" px={5}>
104
+ {icon}
105
+ {typeof label === 'string' ? <Text appearance="body2" className="section-label" ml={2}>{label}</Text> : label.element}
106
+ </Flex>
107
+ </a>
108
+ {shouldShowOverlay() &&
109
+ <IconBox size="sm" className="section-submenu-icon"
110
+ as="button"
111
+ aria-label={interpolate(t.menuOptions, label)}
112
+ aria-controls={MENU_OVERLAY_ID}
113
+ aria-expanded={document.getElementById(MENU_OVERLAY_ID)?.classList.contains('visible')}
114
+ onKeyDown={(event) => {
115
+ if (event.key === 'Enter') {
116
+ showOverlayAndFixArrowPosition(event)
117
+ }
118
+ }
119
+ }>
120
+ <ChevronRight />
121
+ </IconBox>
122
+ }
123
+ </li>
124
+ </>
100
125
  )
101
126
  }
102
127
 
@@ -104,15 +129,17 @@ const OverlayRenderer = ({ content, customContent }: Pick<MenuSection, 'content'
104
129
  if (customContent) {
105
130
  return <> {customContent} </>
106
131
  }
107
-
132
+
108
133
  const data = typeof content === 'function' ? content() : content
109
- return <div><MenuContent {...data} /></div>
134
+ return <MenuContent {...data} />
110
135
  }
111
136
 
112
137
  export const MenuSections = ({ sections = [], ...props }: MenuProps) => {
113
138
  const t = useTranslate(dictionary)
114
139
  // this is a mock state only used to force an update on the component.
115
140
  const [_, setUpdate] = useState(0)
141
+ const onHide = () => hideOverlay()
142
+
116
143
  const toggleMenu = useCallback(() => {
117
144
  const layout = document.getElementById('layout')
118
145
  if (!layout) return
@@ -132,6 +159,20 @@ export const MenuSections = ({ sections = [], ...props }: MenuProps) => {
132
159
  [sections],
133
160
  )
134
161
 
162
+ function onPressEscape() {
163
+ hideOverlay()
164
+ const items = document.getElementsByClassName('section-submenu')
165
+ if (!!items && !!currentOverlay && items.length > currentOverlay && items[currentOverlay].children.length > 1) {
166
+ (items[currentOverlay].children[1] as HTMLElement).focus()
167
+ }
168
+ }
169
+
170
+ const wrapper = useKeyboardControls({
171
+ onHide, visible: document.getElementById(MENU_OVERLAY_ID)?.classList.contains('visible') || false,
172
+ querySelectors: 'li a.action, #custom-selectable-item button, #custom-selectable-item input',
173
+ onPressEscape: () => onPressEscape(),
174
+ })
175
+
135
176
  /* This function renders the section preview in the overlay in normal circumstances. If the menu is hidden and the section is active,
136
177
  instead of rendering the section preview, it will render the actual menu content, which would be invisible otherwise.
137
178
  Below, the key is of extreme importance. It ensures React will consider every section content to be an entirely different
@@ -139,26 +180,43 @@ export const MenuSections = ({ sections = [], ...props }: MenuProps) => {
139
180
  hook, this would cause some serious problems. */
140
181
  function renderMenuOverlay() {
141
182
  if (currentOverlay === undefined) return null
142
- const shouldRenderMenuContentInstead = !isMenuContentVisible() && sections[currentOverlay].active &&
183
+ const shouldRenderMenuContentInstead = !isMenuContentVisible() && sections[currentOverlay].active &&
143
184
  (!!props.content || !!props.customContent)
144
185
  return shouldRenderMenuContentInstead
145
186
  ? <OverlayRenderer key={'contentKey' in props ? props.contentKey : undefined} content={props.content}
146
187
  customContent={props.customContent} />
147
- : <OverlayRenderer key={currentOverlay} content={sections[currentOverlay].content}
188
+ : <OverlayRenderer key={currentOverlay} content={sections[currentOverlay].content}
148
189
  customContent={sections[currentOverlay].customContent} />
149
190
  }
150
191
 
151
192
  return (
152
193
  <>
153
194
  <ul>{sectionItems}</ul>
154
- {(!!props.content || !!props.customContent) &&
155
- <button className="toggle" onClick={toggleMenu} title={t.toggle} tabIndex={-1} aria-hidden>
156
- <IconBox>
157
- <MenuIcon className="expand" />
158
- <ChevronLeft className="collapse" />
159
- </IconBox>
160
- </button>}
161
- <div id="menuContentOverlay" onMouseEnter={showOverlay} onMouseLeave={hideOverlay}>
195
+
196
+ {(!!props.content || !!props.customContent || props.settings?.show) &&
197
+ <Flex mb={7} alignItems="center" justifyContent="center">
198
+ {(!!props.content || !!props.customContent) &&
199
+ <button className="toggle sections-footer" onClick={toggleMenu} title={t.toggle} tabIndex={-1} aria-hidden>
200
+ <IconBox>
201
+ <MenuIcon className="expand" />
202
+ <Collapse className="collapse" />
203
+ </IconBox>
204
+ </button>}
205
+ {(props.settings?.show) &&
206
+ <a href={props.settings?.href} onClick={props.settings?.onClick}
207
+ className="sections-footer"
208
+ {...(props.settings.active ? { 'aria-current': 'page' } : undefined)}>
209
+ <Flex alignItems="center" justifyContent="center">
210
+ <IconBox aria-label={t.settingsIcon}>
211
+ <Cog />
212
+ </IconBox>
213
+ <Text appearance="body2" ml={2}>{t.settings}</Text>
214
+ </Flex>
215
+ </a>
216
+ }
217
+ </Flex>
218
+ }
219
+ <div id={MENU_OVERLAY_ID} onMouseEnter={showOverlay} onMouseLeave={hideOverlay} ref={wrapper}>
162
220
  {renderMenuOverlay()}
163
221
  <div className="arrow"></div>
164
222
  </div>
@@ -169,8 +227,14 @@ export const MenuSections = ({ sections = [], ...props }: MenuProps) => {
169
227
  const dictionary = {
170
228
  en: {
171
229
  toggle: 'Show or hide the menu',
230
+ menuOptions: 'View $0 menu options',
231
+ settings: 'Settings',
232
+ settingsIcon: 'Settings icon',
172
233
  },
173
234
  pt: {
174
235
  toggle: 'Visualizar ou esconder o menu',
236
+ menuOptions: 'Visualizar opções do menu $0',
237
+ settings: 'Configurações',
238
+ settingsIcon: 'Icone de configurações',
175
239
  },
176
240
  } satisfies Dictionary
@@ -8,7 +8,7 @@ import { styled } from 'styled-components'
8
8
  import { ListAction, SelectionList } from '../SelectionList'
9
9
  import { MENU_CONTENT_PADDING as PADDING } from './constants'
10
10
  import { Selector } from './types'
11
- import { useCheckTextOverflow } from './useCheckTextOverflow'
11
+ import { useCheckTextOverflow } from './use-check-text-overflow'
12
12
 
13
13
  const SelectorBox = styled.div`
14
14
  position: relative;
@@ -96,13 +96,16 @@ export const PageSelector = ({ options, value, button, loading, title }: Selecto
96
96
  },
97
97
  [options, value, button],
98
98
  )
99
-
99
+
100
100
  const label = selected?.label ?? button?.label ?? value
101
+ const isTextLabel = typeof label == 'string'
102
+ const labelText = typeof label === 'string' ? label : label.id
103
+ const buttonLabelText = typeof button?.label == 'string' ? button?.label : button?.label.id
101
104
 
102
105
  return (
103
106
  <SelectorBox>
104
107
  {loading
105
- ? <LoadingCircular />
108
+ ? <LoadingCircular />
106
109
  : (
107
110
  <>
108
111
  {title && <Text colorScheme="light.700" sx={{ mb: 3 }}>{title}</Text>}
@@ -116,7 +119,9 @@ export const PageSelector = ({ options, value, button, loading, title }: Selecto
116
119
  aria-controls={id.current}
117
120
  >
118
121
  {selected?.icon && <IconBox>{selected?.icon}</IconBox>}
119
- <Text ref={ref} appearance="body2" className="label" title={overflow ? label : ''}>{label}</Text>
122
+ {isTextLabel ?
123
+ <Text ref={ref} appearance="body2" className="label" title={overflow ? labelText : ''}>{labelText}</Text> :
124
+ label.element}
120
125
  <IconBox size="xs"><Select /></IconBox>
121
126
  </a>
122
127
 
@@ -125,7 +130,7 @@ export const PageSelector = ({ options, value, button, loading, title }: Selecto
125
130
  visible={visible}
126
131
  items={optionsWithIcon}
127
132
  onHide={() => setVisible(false)}
128
- after={button ? <a className="view-all" href={button.href} onClick={button.onClick}>{button.label}</a> : undefined}
133
+ after={button ? <a className="view-all" href={button.href} onClick={button.onClick}>{buttonLabelText}</a> : undefined}
129
134
  scroll
130
135
  />
131
136
  </>
@@ -76,12 +76,20 @@ export interface MenuSection extends Action {
76
76
  customContent?: ReactNode,
77
77
  active?: boolean,
78
78
  onOpen?: () => void,
79
+ className?: string,
79
80
  }
80
81
 
81
82
  interface BaseMenuProps {
82
83
  sections?: MenuSection[],
83
84
  compact?: boolean,
84
85
  customContent?: ReactNode,
86
+ settings?: {
87
+ show?: boolean,
88
+ onClick?: () => void,
89
+ href?: string,
90
+ active?: boolean,
91
+ className?: string,
92
+ },
85
93
  }
86
94
 
87
95
  export interface MenuPropsWithStaticContent extends BaseMenuProps {
@@ -1,4 +1,4 @@
1
- import { useState, useRef, useEffect } from 'react';
1
+ import { useState, useRef, useEffect } from 'react'
2
2
 
3
3
  export function useCheckTextOverflow() {
4
4
  const [overflow, setOverflow] = useState<boolean>(false)
@@ -23,4 +23,4 @@ export function useCheckTextOverflow() {
23
23
  }, [ref.current])
24
24
 
25
25
  return { overflow, ref }
26
- }
26
+ }
@@ -0,0 +1,88 @@
1
+ import { useCallback, useEffect, useRef } from 'react'
2
+
3
+ interface Props {
4
+ onHide?: () => void,
5
+ visible: boolean,
6
+ querySelectors: string,
7
+ onPressEscape?: () => void,
8
+ }
9
+
10
+ export function useKeyboardControls({ onHide, visible, querySelectors, onPressEscape }: Props) {
11
+ const wrapper = useRef<HTMLDivElement>(null)
12
+
13
+ const onRemoveListeners = () =>{
14
+ document.removeEventListener('keydown', keyboardControls)
15
+ document.removeEventListener('click', hide)
16
+ }
17
+
18
+ const keyboardControls = useCallback((event: KeyboardEvent) => {
19
+ const target = event?.target as HTMLElement | null
20
+
21
+ function getSelectableAnchors() {
22
+ return wrapper.current?.querySelectorAll(querySelectors) ?? []
23
+ }
24
+
25
+ function handleArrows(key = event.key) {
26
+
27
+ const anchors = getSelectableAnchors()
28
+ let i = 0
29
+ while (i < anchors.length && document.activeElement !== anchors[i]) i++
30
+ const next: any = key === 'ArrowDown' ? (anchors[i + 1] ?? anchors[0]) : (anchors[i - 1] ?? anchors[anchors.length - 1])
31
+ next?.focus?.()
32
+ }
33
+
34
+ const handlers: Record<string, (() => void) | undefined> = {
35
+ Escape: () => {
36
+ onPressEscape?.()
37
+ onHide?.()
38
+ onRemoveListeners()
39
+ },
40
+ Enter: () => {
41
+ target?.click()
42
+ },
43
+ Tab: () => {
44
+ const anchors = getSelectableAnchors()
45
+ if (document.activeElement === anchors[anchors.length - 1]) onHide?.()
46
+ else {
47
+ handleArrows('ArrowDown')
48
+ event.preventDefault()
49
+ }
50
+ },
51
+ ArrowUp: () => {
52
+ handleArrows()
53
+ },
54
+ ArrowDown: () => {
55
+ handleArrows()
56
+ },
57
+ }
58
+
59
+ handlers[event.key]?.()
60
+ }, [onPressEscape, visible])
61
+
62
+ const hide = useCallback((event: Event) => {
63
+ const target = (event.target as HTMLElement | null)
64
+ // if the element is not in the DOM anymore, we'll consider the click was inside the selection list
65
+ const isClickInsideSelectionList = !target?.isConnected || wrapper.current?.contains(target)
66
+ const isAction = target?.classList?.contains('action') || !!target?.closest('.action')
67
+ if (!isClickInsideSelectionList || isAction) onHide?.()
68
+ }, [])
69
+
70
+ useEffect(() => {
71
+ if (visible) {
72
+ document.addEventListener('keydown', keyboardControls)
73
+ document.addEventListener('keydown', keyboardControls)
74
+ if (onHide) setTimeout(() => document.addEventListener('click', hide), 50)
75
+ }
76
+ else {
77
+ onRemoveListeners()
78
+ }
79
+
80
+ return () => {
81
+ // Remove the event listener
82
+ document.removeEventListener('keydown', keyboardControls)
83
+ document.removeEventListener('click', hide)
84
+ }
85
+ }, [visible, keyboardControls])
86
+
87
+ return wrapper
88
+ }
@@ -1,7 +1,11 @@
1
- import React from 'react'
1
+ import React, { ReactNode } from 'react'
2
2
 
3
+ interface CustomLabel {
4
+ id: string,
5
+ element: ReactNode,
6
+ }
3
7
  export interface Action {
4
- label: string,
8
+ label: string | CustomLabel,
5
9
  onClick?: () => void,
6
10
  href?: string,
7
11
  target?: React.AnchorHTMLAttributes<HTMLAnchorElement>['target'],
package/src/layout.css CHANGED
@@ -39,7 +39,7 @@ body {
39
39
 
40
40
  #layout {
41
41
  --header-height: 56px;
42
- --menu-sections-width: 87px;
42
+ --menu-sections-width: 135px;
43
43
  --menu-content-width: 233px;
44
44
  --menu-item-height: 74px;
45
45
  --modal-animation-duration: 0.3s;
@@ -50,7 +50,7 @@ body {
50
50
  }
51
51
 
52
52
  #layout.menu-compact {
53
- --menu-sections-width: 56px;
53
+ --menu-sections-width: 135px;
54
54
  --menu-item-height: 56px;
55
55
  }
56
56
 
@@ -147,8 +147,8 @@ body {
147
147
  position: relative;
148
148
  }
149
149
 
150
- #layout.menu-compact .section-label {
151
- display: none;
150
+ #menuSections .sections-footer {
151
+ padding: 16px;
152
152
  }
153
153
 
154
154
  #menuSections .toggle,
@@ -161,7 +161,7 @@ body {
161
161
  display: flex;
162
162
  flex-direction: column;
163
163
  gap: 10px;
164
- align-items: center;
164
+ align-items: flex-start;
165
165
  justify-content: center;
166
166
  transition: background-color 0.2s;
167
167
  cursor: pointer;
@@ -175,7 +175,6 @@ body {
175
175
  height: 24px;
176
176
  transform: scaleY(0);
177
177
  transition: transform ease-in 0.2s;
178
- background-color: var(--primary-500);
179
178
  border-radius: 50%;
180
179
  left: 0;
181
180
  }
@@ -186,6 +185,7 @@ body {
186
185
 
187
186
  #menuSections > ul li.active a:before {
188
187
  transform: scaleY(1);
188
+ background-color: var(--primary-500);
189
189
  }
190
190
 
191
191
  #menuSections .toggle:hover,
@@ -195,6 +195,11 @@ body {
195
195
  background: var(--light-500);
196
196
  }
197
197
 
198
+ #menuSections > ul li:not(.active) a:hover:before {
199
+ transform: scaleY(1);
200
+ background-color: var(--light-contrastText);
201
+ }
202
+
198
203
  #menuSections .toggle i {
199
204
  position: relative;
200
205
  }
@@ -430,3 +435,19 @@ i {
430
435
  height: 0;
431
436
  overflow: hidden;
432
437
  }
438
+
439
+ #menuSections .section-submenu {
440
+ position: relative;
441
+ }
442
+
443
+ #menuSections .section-submenu-icon {
444
+ opacity: 0;
445
+ position: absolute;
446
+ top: 27%;
447
+ right: 10px;
448
+ background-color: inherit;
449
+ }
450
+
451
+ #menuSections .section-submenu-icon:focus-visible {
452
+ opacity: 1;
453
+ }