@stack-spot/portal-layout 0.0.33 → 0.0.35
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 +1 -2
- package/dist/Layout.d.ts.map +1 -1
- package/dist/Layout.js +7 -5
- package/dist/Layout.js.map +1 -1
- package/dist/LayoutOverlayManager.js.map +1 -1
- package/dist/components/SelectionList.d.ts.map +1 -1
- package/dist/components/SelectionList.js +22 -2
- package/dist/components/SelectionList.js.map +1 -1
- package/dist/components/menu/MenuContent.d.ts +2 -545
- package/dist/components/menu/MenuContent.d.ts.map +1 -1
- package/dist/components/menu/MenuContent.js.map +1 -1
- package/dist/components/menu/MenuSections.d.ts.map +1 -1
- package/dist/components/menu/MenuSections.js +43 -33
- package/dist/components/menu/MenuSections.js.map +1 -1
- package/dist/components/menu/PageSelector.js.map +1 -1
- package/dist/components/menu/types.d.ts +0 -1
- package/dist/components/menu/types.d.ts.map +1 -1
- package/dist/components/menu/use-check-text-overflow.js.map +1 -1
- package/dist/components/menu/use-keyboard-controls.d.ts +16 -3
- package/dist/components/menu/use-keyboard-controls.d.ts.map +1 -1
- package/dist/components/menu/use-keyboard-controls.js +21 -46
- package/dist/components/menu/use-keyboard-controls.js.map +1 -1
- package/dist/layout.css +24 -27
- package/dist/utils.js.map +1 -1
- package/package.json +5 -5
- package/src/Layout.tsx +7 -6
- package/src/components/SelectionList.tsx +24 -2
- package/src/components/menu/MenuSections.tsx +49 -37
- package/src/components/menu/types.ts +0 -1
- package/src/components/menu/use-keyboard-controls.tsx +32 -50
- package/src/layout.css +24 -27
- package/dist/components/error/ErrorDescriptor.d.ts +0 -12
- package/dist/components/error/ErrorDescriptor.d.ts.map +0 -1
- package/dist/components/error/ErrorDescriptor.js +0 -17
- package/dist/components/error/ErrorDescriptor.js.map +0 -1
- package/dist/components/menu/useCheckTextOverflow.d.ts +0 -6
- package/dist/components/menu/useCheckTextOverflow.d.ts.map +0 -1
- package/dist/components/menu/useCheckTextOverflow.js +0 -20
- package/dist/components/menu/useCheckTextOverflow.js.map +0 -1
|
@@ -3,7 +3,7 @@ import { ArrowLeft, Check, ChevronRight } from '@citric/icons'
|
|
|
3
3
|
import { IconButton } from '@citric/ui'
|
|
4
4
|
import { WithStyle, listToClass, theme } from '@stack-spot/portal-theme'
|
|
5
5
|
import { Dictionary, useTranslate } from '@stack-spot/portal-translate'
|
|
6
|
-
import { ReactElement, useMemo, useState } from 'react'
|
|
6
|
+
import { ReactElement, useCallback, useEffect, useMemo, useState } from 'react'
|
|
7
7
|
import { styled } from 'styled-components'
|
|
8
8
|
import { useKeyboardControls } from './menu/use-keyboard-controls'
|
|
9
9
|
import { Action } from './types'
|
|
@@ -174,6 +174,9 @@ export const SelectionList = ({
|
|
|
174
174
|
}: SelectionListProps) => {
|
|
175
175
|
const t = useTranslate(dictionary)
|
|
176
176
|
const [current, setCurrent] = useState<CurrentItemList>({ items })
|
|
177
|
+
const { keyboardControlledElement: wrapper, attachKeyboardListeners, detachKeyboardListeners } = useKeyboardControls(
|
|
178
|
+
{ onPressEscape: onHide, querySelectors: 'li.action a, li.collapsible a, button' },
|
|
179
|
+
)
|
|
177
180
|
|
|
178
181
|
const listItems = useMemo(
|
|
179
182
|
() => current.items.map(i => renderItem(
|
|
@@ -186,7 +189,26 @@ export const SelectionList = ({
|
|
|
186
189
|
)),
|
|
187
190
|
[current],
|
|
188
191
|
)
|
|
189
|
-
|
|
192
|
+
|
|
193
|
+
const hide = useCallback((event: Event) => {
|
|
194
|
+
const target = (event.target as HTMLElement | null)
|
|
195
|
+
// if the element is not in the DOM anymore, we'll consider the click was inside the selection list
|
|
196
|
+
const isClickInsideSelectionList = !target?.isConnected || wrapper.current?.contains(target)
|
|
197
|
+
const isAction = target?.classList?.contains('action') || !!target?.closest('.action')
|
|
198
|
+
if (!isClickInsideSelectionList || isAction) onHide?.()
|
|
199
|
+
}, [])
|
|
200
|
+
|
|
201
|
+
useEffect(() => {
|
|
202
|
+
if (visible) {
|
|
203
|
+
setCurrent({ items })
|
|
204
|
+
attachKeyboardListeners()
|
|
205
|
+
if (onHide) setTimeout(() => document.addEventListener('click', hide), 50)
|
|
206
|
+
}
|
|
207
|
+
else {
|
|
208
|
+
detachKeyboardListeners()
|
|
209
|
+
document.removeEventListener('click', hide)
|
|
210
|
+
}
|
|
211
|
+
}, [visible])
|
|
190
212
|
|
|
191
213
|
return (
|
|
192
214
|
<SelectionBox
|
|
@@ -2,24 +2,38 @@ import { Flex, IconBox, Text } from '@citric/core'
|
|
|
2
2
|
import { ChevronRight, Cog, Collapse, Expand } from '@citric/icons'
|
|
3
3
|
import { Dictionary, interpolate, useTranslate } from '@stack-spot/portal-translate'
|
|
4
4
|
import { useCallback, useMemo, useState } from 'react'
|
|
5
|
+
import { getLayoutElements } from '../../elements'
|
|
6
|
+
import { elementIds } from '../../elements'
|
|
5
7
|
import { MenuContent } from './MenuContent'
|
|
6
8
|
import { MenuProps, MenuSection } from './types'
|
|
7
9
|
import { useKeyboardControls } from './use-keyboard-controls'
|
|
8
10
|
|
|
9
|
-
const ARROW_HEIGHT = 24
|
|
10
11
|
const HIDE_OVERLAY_DELAY_MS = 400
|
|
11
12
|
const MENU_OVERLAY_ID = 'menuContentOverlay'
|
|
12
13
|
|
|
13
14
|
let hideOverlayTask: number | undefined
|
|
14
15
|
|
|
16
|
+
// fixme: this should definitely not be handled like this...
|
|
17
|
+
let attachKeyboardListenersForOverlay: () => void
|
|
18
|
+
let detachKeyboardListenersForOverlay: () => void
|
|
19
|
+
|
|
15
20
|
function hideOverlay() {
|
|
16
21
|
if (hideOverlayTask !== undefined) return
|
|
17
22
|
hideOverlayTask = window.setTimeout(hideOverlayImmediately, HIDE_OVERLAY_DELAY_MS)
|
|
18
23
|
}
|
|
19
24
|
|
|
25
|
+
function getAccessibilityButtonOfSectionWithActiveOverlay(): HTMLElement | null | undefined {
|
|
26
|
+
return document.getElementById(elementIds.menuSections)?.querySelector('button[aria-expanded="true"]')
|
|
27
|
+
}
|
|
28
|
+
|
|
20
29
|
// eslint-disable-next-line react-refresh/only-export-components
|
|
21
30
|
export function hideOverlayImmediately() {
|
|
22
|
-
|
|
31
|
+
detachKeyboardListenersForOverlay?.()
|
|
32
|
+
const overlay = document.getElementById(MENU_OVERLAY_ID)
|
|
33
|
+
overlay?.setAttribute('inert', '')
|
|
34
|
+
overlay?.setAttribute('aria-hidden', '')
|
|
35
|
+
overlay?.classList.remove('visible')
|
|
36
|
+
getAccessibilityButtonOfSectionWithActiveOverlay()?.setAttribute('aria-expanded', 'false')
|
|
23
37
|
}
|
|
24
38
|
|
|
25
39
|
function cancelHideOverlayTask() {
|
|
@@ -30,7 +44,11 @@ function cancelHideOverlayTask() {
|
|
|
30
44
|
|
|
31
45
|
function showOverlay() {
|
|
32
46
|
cancelHideOverlayTask()
|
|
33
|
-
document.getElementById(MENU_OVERLAY_ID)
|
|
47
|
+
const overlay = document.getElementById(MENU_OVERLAY_ID)
|
|
48
|
+
overlay?.removeAttribute('inert')
|
|
49
|
+
overlay?.removeAttribute('aria-hidden')
|
|
50
|
+
overlay?.classList.add('visible')
|
|
51
|
+
attachKeyboardListenersForOverlay?.()
|
|
34
52
|
}
|
|
35
53
|
|
|
36
54
|
function isMenuContentVisible() {
|
|
@@ -60,25 +78,21 @@ const Section = ({
|
|
|
60
78
|
/* The overlay should appear if:
|
|
61
79
|
* 1. The menu is compacted showing only the icons
|
|
62
80
|
* 2. The section has some content to render OR:
|
|
63
|
-
*
|
|
64
|
-
* 3. The section is inactive OR:
|
|
65
|
-
* 2.1. The contextual menu is hidden.
|
|
81
|
+
* 3. The section is active and there is a contextual menu for the active page.
|
|
66
82
|
*/
|
|
67
|
-
const layout =
|
|
68
|
-
const isCompactedOnlyIcons = layout?.classList.contains('menu-compact
|
|
69
|
-
return isCompactedOnlyIcons && (!!contentToRender || !!customContent || (hasContent && active))
|
|
83
|
+
const { layout } = getLayoutElements()
|
|
84
|
+
const isCompactedOnlyIcons = layout?.classList.contains('menu-compact')
|
|
85
|
+
return isCompactedOnlyIcons && (!!contentToRender || !!customContent || (hasContent && active))
|
|
70
86
|
}
|
|
71
87
|
|
|
72
|
-
function
|
|
88
|
+
function prepareShowOverlay(event: React.MouseEvent<HTMLAnchorElement, MouseEvent> | React.KeyboardEvent<any>) {
|
|
73
89
|
if (!shouldShowOverlay()) return
|
|
74
90
|
onOpen?.()
|
|
75
|
-
const
|
|
76
|
-
const
|
|
91
|
+
const anchorElement = event.target as HTMLElement
|
|
92
|
+
const accessibilityButton = anchorElement?.parentElement?.querySelector('button') as HTMLElement
|
|
93
|
+
accessibilityButton?.setAttribute('aria-expanded', 'true')
|
|
77
94
|
setCurrentOverlay(id)
|
|
78
95
|
showOverlay()
|
|
79
|
-
if (rect && arrow) {
|
|
80
|
-
arrow.style.top = `${rect.top + rect.height / 2 - ARROW_HEIGHT / 2}px`
|
|
81
|
-
}
|
|
82
96
|
}
|
|
83
97
|
|
|
84
98
|
function click() {
|
|
@@ -95,7 +109,7 @@ const Section = ({
|
|
|
95
109
|
href={href}
|
|
96
110
|
target={target}
|
|
97
111
|
onClick={click}
|
|
98
|
-
onMouseEnter={
|
|
112
|
+
onMouseEnter={prepareShowOverlay}
|
|
99
113
|
onMouseLeave={() => shouldShowOverlay() && hideOverlay()}
|
|
100
114
|
title={labelText}
|
|
101
115
|
aria-label={labelText}
|
|
@@ -104,7 +118,7 @@ const Section = ({
|
|
|
104
118
|
{...(!href ? { 'tabIndex': 0 } : undefined)}
|
|
105
119
|
>
|
|
106
120
|
<Flex alignItems="center" justifyContent="center" px={5}>
|
|
107
|
-
{icon}
|
|
121
|
+
<IconBox>{icon}</IconBox>
|
|
108
122
|
{typeof label === 'string' ? <Text appearance="microtext1" className="section-label" ml={3}>{label}</Text> : label.element}
|
|
109
123
|
</Flex>
|
|
110
124
|
</a>
|
|
@@ -113,13 +127,12 @@ const Section = ({
|
|
|
113
127
|
as="button"
|
|
114
128
|
aria-label={interpolate(t.menuOptions, label)}
|
|
115
129
|
aria-controls={MENU_OVERLAY_ID}
|
|
116
|
-
aria-expanded={
|
|
130
|
+
aria-expanded={false}
|
|
117
131
|
onKeyDown={(event) => {
|
|
118
132
|
if (event.key === 'Enter') {
|
|
119
|
-
|
|
133
|
+
prepareShowOverlay(event)
|
|
120
134
|
}
|
|
121
|
-
}
|
|
122
|
-
}>
|
|
135
|
+
}}>
|
|
123
136
|
<ChevronRight />
|
|
124
137
|
</IconBox>
|
|
125
138
|
}
|
|
@@ -130,7 +143,7 @@ const Section = ({
|
|
|
130
143
|
|
|
131
144
|
const OverlayRenderer = ({ content, customContent }: Pick<MenuSection, 'content' | 'customContent'>) => {
|
|
132
145
|
if (customContent) {
|
|
133
|
-
return
|
|
146
|
+
return <div id="custom-selectable-item"> {customContent} </div>
|
|
134
147
|
}
|
|
135
148
|
|
|
136
149
|
const data = typeof content === 'function' ? content() : content
|
|
@@ -141,15 +154,14 @@ export const MenuSections = ({ sections = [], ...props }: MenuProps) => {
|
|
|
141
154
|
const t = useTranslate(dictionary)
|
|
142
155
|
// this is a mock state only used to force an update on the component.
|
|
143
156
|
const [_, setUpdate] = useState(0)
|
|
144
|
-
const onHide = () => hideOverlay()
|
|
145
157
|
|
|
146
158
|
const toggleMenu = useCallback((hasContent: boolean) => {
|
|
147
159
|
const layout = document.getElementById('layout')
|
|
148
160
|
if (!layout) return
|
|
149
|
-
if (layout.classList.contains('menu-compact
|
|
150
|
-
layout.classList.remove('menu-compact
|
|
161
|
+
if (layout.classList.contains('menu-compact')) {
|
|
162
|
+
layout.classList.remove('menu-compact')
|
|
151
163
|
} else {
|
|
152
|
-
layout.classList.add('menu-compact
|
|
164
|
+
layout.classList.add('menu-compact')
|
|
153
165
|
}
|
|
154
166
|
|
|
155
167
|
if (hasContent) {
|
|
@@ -171,19 +183,19 @@ export const MenuSections = ({ sections = [], ...props }: MenuProps) => {
|
|
|
171
183
|
)
|
|
172
184
|
|
|
173
185
|
function onPressEscape() {
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
if (!!items && !!currentOverlay && items.length > currentOverlay && items[currentOverlay].children.length > 1) {
|
|
177
|
-
(items[currentOverlay].children[1] as HTMLElement).focus()
|
|
178
|
-
}
|
|
186
|
+
getAccessibilityButtonOfSectionWithActiveOverlay()?.focus()
|
|
187
|
+
hideOverlayImmediately()
|
|
179
188
|
}
|
|
180
189
|
|
|
181
|
-
const
|
|
182
|
-
|
|
190
|
+
const { keyboardControlledElement: overlayRef, attachKeyboardListeners, detachKeyboardListeners } = useKeyboardControls({
|
|
191
|
+
onPressEscape,
|
|
183
192
|
querySelectors: 'li a.action, #custom-selectable-item button, #custom-selectable-item input',
|
|
184
|
-
onPressEscape: () => onPressEscape(),
|
|
185
193
|
})
|
|
186
194
|
|
|
195
|
+
// fixme: this should definitely not be handled like this...
|
|
196
|
+
attachKeyboardListenersForOverlay = attachKeyboardListeners
|
|
197
|
+
detachKeyboardListenersForOverlay = detachKeyboardListeners
|
|
198
|
+
|
|
187
199
|
/* This function renders the section preview in the overlay in normal circumstances. If the menu is hidden and the section is active,
|
|
188
200
|
instead of rendering the section preview, it will render the actual menu content, which would be invisible otherwise.
|
|
189
201
|
Below, the key is of extreme importance. It ensures React will consider every section content to be an entirely different
|
|
@@ -206,7 +218,7 @@ export const MenuSections = ({ sections = [], ...props }: MenuProps) => {
|
|
|
206
218
|
|
|
207
219
|
<Flex mb={7} alignItems="center">
|
|
208
220
|
<button className="toggle sections-footer" onClick={() => toggleMenu(!!props.content || !!props.customContent)}
|
|
209
|
-
title={t.toggle} tabIndex={-1}
|
|
221
|
+
title={t.toggle} tabIndex={-1}>
|
|
210
222
|
<IconBox>
|
|
211
223
|
<Expand className="expand" />
|
|
212
224
|
<Collapse className="collapse" />
|
|
@@ -217,7 +229,7 @@ export const MenuSections = ({ sections = [], ...props }: MenuProps) => {
|
|
|
217
229
|
<a href={props.settings?.href} onClick={props.settings?.onClick}
|
|
218
230
|
className="sections-footer"
|
|
219
231
|
{...(props.settings.active ? { 'aria-current': 'page' } : undefined)}>
|
|
220
|
-
<Flex alignItems="center" >
|
|
232
|
+
<Flex alignItems="center" justifyContent="center">
|
|
221
233
|
<IconBox aria-label={t.settingsIcon}>
|
|
222
234
|
<Cog />
|
|
223
235
|
</IconBox>
|
|
@@ -227,7 +239,7 @@ export const MenuSections = ({ sections = [], ...props }: MenuProps) => {
|
|
|
227
239
|
}
|
|
228
240
|
</Flex>
|
|
229
241
|
|
|
230
|
-
<div id={MENU_OVERLAY_ID} onMouseEnter={showOverlay} onMouseLeave={hideOverlay} ref={
|
|
242
|
+
<div id={MENU_OVERLAY_ID} onMouseEnter={showOverlay} onMouseLeave={hideOverlay} ref={overlayRef}>
|
|
231
243
|
{renderMenuOverlay()}
|
|
232
244
|
<div className="arrow"></div>
|
|
233
245
|
</div>
|
|
@@ -1,29 +1,34 @@
|
|
|
1
|
-
import { useCallback,
|
|
1
|
+
import { useCallback, useRef } from 'react'
|
|
2
2
|
|
|
3
3
|
interface Props {
|
|
4
|
-
|
|
5
|
-
|
|
4
|
+
/**
|
|
5
|
+
* A query selector that returns every html element that must be navigable through the keyboard.
|
|
6
|
+
*/
|
|
6
7
|
querySelectors: string,
|
|
8
|
+
/**
|
|
9
|
+
* Function to call when ESC is pressed. or when TAB is pressed at the last item in the list of items returned by the query selector.
|
|
10
|
+
*/
|
|
7
11
|
onPressEscape?: () => void,
|
|
12
|
+
/**
|
|
13
|
+
* Function to call when TAB is pressed at the last item in the list of items returned by the query selector. Will be the same as
|
|
14
|
+
* onPressEscape if not specified.
|
|
15
|
+
*/
|
|
16
|
+
onPressLastTab?: () => void,
|
|
8
17
|
}
|
|
9
18
|
|
|
10
|
-
export function useKeyboardControls({
|
|
11
|
-
const
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
document.removeEventListener('keydown', keyboardControls)
|
|
15
|
-
document.removeEventListener('click', hide)
|
|
16
|
-
}
|
|
19
|
+
export function useKeyboardControls({ querySelectors, onPressEscape, onPressLastTab = onPressEscape }: Props) {
|
|
20
|
+
const keyboardControlledElement = useRef<HTMLDivElement>(null)
|
|
21
|
+
const listeners = useRef<Pick<Props, 'onPressEscape' | 'onPressLastTab'>>({})
|
|
22
|
+
listeners.current = { onPressEscape, onPressLastTab }
|
|
17
23
|
|
|
18
24
|
const keyboardControls = useCallback((event: KeyboardEvent) => {
|
|
19
25
|
const target = event?.target as HTMLElement | null
|
|
20
26
|
|
|
21
27
|
function getSelectableAnchors() {
|
|
22
|
-
return
|
|
28
|
+
return keyboardControlledElement.current?.querySelectorAll(querySelectors) ?? []
|
|
23
29
|
}
|
|
24
30
|
|
|
25
31
|
function handleArrows(key = event.key) {
|
|
26
|
-
|
|
27
32
|
const anchors = getSelectableAnchors()
|
|
28
33
|
let i = 0
|
|
29
34
|
while (i < anchors.length && document.activeElement !== anchors[i]) i++
|
|
@@ -33,56 +38,33 @@ export function useKeyboardControls({ onHide, visible, querySelectors, onPressEs
|
|
|
33
38
|
|
|
34
39
|
const handlers: Record<string, (() => void) | undefined> = {
|
|
35
40
|
Escape: () => {
|
|
36
|
-
onPressEscape?.()
|
|
37
|
-
|
|
38
|
-
|
|
41
|
+
listeners.current.onPressEscape?.()
|
|
42
|
+
event.stopPropagation()
|
|
43
|
+
event.preventDefault()
|
|
39
44
|
},
|
|
40
45
|
Enter: () => {
|
|
41
46
|
target?.click()
|
|
42
47
|
},
|
|
43
48
|
Tab: () => {
|
|
44
49
|
const anchors = getSelectableAnchors()
|
|
45
|
-
if (document.activeElement === anchors[anchors.length - 1])
|
|
46
|
-
else
|
|
47
|
-
|
|
48
|
-
event.preventDefault()
|
|
49
|
-
}
|
|
50
|
-
},
|
|
51
|
-
ArrowUp: () => {
|
|
52
|
-
handleArrows()
|
|
53
|
-
},
|
|
54
|
-
ArrowDown: () => {
|
|
55
|
-
handleArrows()
|
|
50
|
+
if (document.activeElement === anchors[anchors.length - 1]) listeners.current.onPressLastTab?.()
|
|
51
|
+
else handleArrows('ArrowDown')
|
|
52
|
+
event.preventDefault()
|
|
56
53
|
},
|
|
54
|
+
ArrowUp: handleArrows,
|
|
55
|
+
ArrowDown: handleArrows,
|
|
57
56
|
}
|
|
58
57
|
|
|
59
58
|
handlers[event.key]?.()
|
|
60
|
-
}, [
|
|
59
|
+
}, [])
|
|
61
60
|
|
|
62
|
-
const
|
|
63
|
-
|
|
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?.()
|
|
61
|
+
const attachKeyboardListeners = useCallback(() => {
|
|
62
|
+
document.addEventListener('keydown', keyboardControls)
|
|
68
63
|
}, [])
|
|
69
64
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
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])
|
|
65
|
+
const detachKeyboardListeners = useCallback(() => {
|
|
66
|
+
document.removeEventListener('keydown', keyboardControls)
|
|
67
|
+
}, [])
|
|
86
68
|
|
|
87
|
-
return
|
|
69
|
+
return { keyboardControlledElement, attachKeyboardListeners, detachKeyboardListeners }
|
|
88
70
|
}
|
package/src/layout.css
CHANGED
|
@@ -45,16 +45,16 @@ body {
|
|
|
45
45
|
--modal-animation-duration: 0.3s;
|
|
46
46
|
--right-panel-animation-duration: 0.3s;
|
|
47
47
|
--menu-animation-duration: 0.3s;
|
|
48
|
-
--menu-overlay-width:
|
|
48
|
+
--menu-overlay-width: 240px;
|
|
49
49
|
--toastify-font-family: 'Roboto', sans-serif;
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
-
#layout
|
|
52
|
+
#layout:not(.menu-compact) {
|
|
53
53
|
--menu-sections-width: 135px;
|
|
54
54
|
--menu-item-height: 56px;
|
|
55
55
|
}
|
|
56
56
|
|
|
57
|
-
#layout.menu-compact
|
|
57
|
+
#layout.menu-compact {
|
|
58
58
|
--menu-sections-width: 56px;
|
|
59
59
|
--menu-item-height: 56px;
|
|
60
60
|
}
|
|
@@ -87,7 +87,6 @@ body {
|
|
|
87
87
|
display: flex;
|
|
88
88
|
flex-direction: column;
|
|
89
89
|
background-color: var(--light-300);
|
|
90
|
-
border-top-left-radius: 0.5rem;
|
|
91
90
|
align-items: center;
|
|
92
91
|
padding: 24px;
|
|
93
92
|
transition: left ease-in-out var(--menu-animation-duration);
|
|
@@ -133,20 +132,24 @@ body {
|
|
|
133
132
|
}
|
|
134
133
|
|
|
135
134
|
#menu .toggle .expand,
|
|
136
|
-
#layout.menu-compact
|
|
135
|
+
#layout.menu-compact .toggle .collapse {
|
|
137
136
|
opacity: 0;
|
|
138
137
|
}
|
|
139
138
|
|
|
140
139
|
#menu .toggle .collapse,
|
|
141
|
-
#layout.menu-compact
|
|
140
|
+
#layout.menu-compact .toggle .expand {
|
|
142
141
|
opacity: 1;
|
|
143
142
|
}
|
|
144
143
|
|
|
145
|
-
#layout
|
|
144
|
+
#layout .section-label {
|
|
145
|
+
line-height: 0.875rem;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
#layout.menu-compact .section-label {
|
|
146
149
|
display: none;
|
|
147
150
|
}
|
|
148
151
|
|
|
149
|
-
#layout:not(.menu-compact
|
|
152
|
+
#layout:not(.menu-compact) .section-label {
|
|
150
153
|
display: block;
|
|
151
154
|
}
|
|
152
155
|
|
|
@@ -154,10 +157,12 @@ body {
|
|
|
154
157
|
width: var(--menu-sections-width);
|
|
155
158
|
display: flex;
|
|
156
159
|
flex-direction: column;
|
|
157
|
-
padding:
|
|
160
|
+
padding: 0 0 10px;
|
|
158
161
|
justify-content: space-between;
|
|
159
162
|
background-color: var(--light-400);
|
|
160
163
|
position: relative;
|
|
164
|
+
border-right: 1px solid var(--light-300);
|
|
165
|
+
border-top: 1px solid var(--light-300);
|
|
161
166
|
}
|
|
162
167
|
|
|
163
168
|
#menuSections .sections-footer {
|
|
@@ -179,6 +184,7 @@ body {
|
|
|
179
184
|
transition: background-color 0.2s;
|
|
180
185
|
cursor: pointer;
|
|
181
186
|
position: relative;
|
|
187
|
+
z-index: 2;
|
|
182
188
|
}
|
|
183
189
|
|
|
184
190
|
#menuSections > ul li a:before {
|
|
@@ -194,6 +200,7 @@ body {
|
|
|
194
200
|
|
|
195
201
|
#menuSections > ul li.active a {
|
|
196
202
|
background-color: var(--light-500);
|
|
203
|
+
border-right: 1px solid var(--light-300);
|
|
197
204
|
}
|
|
198
205
|
|
|
199
206
|
#menuSections > ul li.active a:before {
|
|
@@ -206,6 +213,7 @@ body {
|
|
|
206
213
|
#menuSections .toggle:focus,
|
|
207
214
|
#menuSections > ul li a:focus {
|
|
208
215
|
background: var(--light-500);
|
|
216
|
+
border-right: 1px solid var(--light-300);
|
|
209
217
|
}
|
|
210
218
|
|
|
211
219
|
#menuSections > ul li:not(.active) a:hover:before {
|
|
@@ -220,11 +228,12 @@ body {
|
|
|
220
228
|
#menuContentOverlay {
|
|
221
229
|
width: var(--menu-overlay-width);
|
|
222
230
|
position: fixed;
|
|
223
|
-
top: calc(var(--header-height)
|
|
224
|
-
left: calc(var(--menu-sections-width)
|
|
225
|
-
bottom:
|
|
231
|
+
top: calc(var(--header-height));
|
|
232
|
+
left: calc(var(--menu-sections-width));
|
|
233
|
+
bottom: 1px;
|
|
226
234
|
background-color: var(--light-500);
|
|
227
|
-
border-radius:
|
|
235
|
+
border-radius: 0px 8px 8px 0px;
|
|
236
|
+
border-top: 1px solid var(--light-300);
|
|
228
237
|
display: flex;
|
|
229
238
|
flex-direction: column;
|
|
230
239
|
overflow: hidden;
|
|
@@ -244,19 +253,6 @@ body {
|
|
|
244
253
|
overflow: auto;
|
|
245
254
|
}
|
|
246
255
|
|
|
247
|
-
#menuContentOverlay > .arrow {
|
|
248
|
-
width: 0;
|
|
249
|
-
height: 0;
|
|
250
|
-
border-top: 12px solid transparent;
|
|
251
|
-
border-bottom: 12px solid transparent;
|
|
252
|
-
border-right: 12px solid var(--light-400);
|
|
253
|
-
position: fixed;
|
|
254
|
-
/* header + menu sections padding + item height / 2 - arrow height / 2 */
|
|
255
|
-
top: calc(var(--header-height) + 10px + var(--menu-item-height) / 2 - 12px);
|
|
256
|
-
left: calc(var(--menu-sections-width) + 3px);
|
|
257
|
-
transition: top ease-out 0.3s;
|
|
258
|
-
}
|
|
259
|
-
|
|
260
256
|
#menuContent {
|
|
261
257
|
width: var(--menu-content-width);
|
|
262
258
|
transition: left ease-out var(--menu-animation-duration);
|
|
@@ -266,7 +262,7 @@ body {
|
|
|
266
262
|
top: 0;
|
|
267
263
|
bottom: 0;
|
|
268
264
|
left: calc(var(--menu-sections-width) - var(--menu-content-width) - 2px); /* 2px from border */
|
|
269
|
-
border-
|
|
265
|
+
border-top: 1px solid var(--light-300);
|
|
270
266
|
}
|
|
271
267
|
|
|
272
268
|
#layout.menu-content-visible #menuContent {
|
|
@@ -458,6 +454,7 @@ i {
|
|
|
458
454
|
top: 27%;
|
|
459
455
|
right: 10px;
|
|
460
456
|
background-color: inherit;
|
|
457
|
+
z-index: 1;
|
|
461
458
|
}
|
|
462
459
|
|
|
463
460
|
#menuSections .section-submenu-icon:focus-visible {
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
export interface ErrorDescription {
|
|
2
|
-
code?: number;
|
|
3
|
-
message?: string;
|
|
4
|
-
debug?: boolean;
|
|
5
|
-
}
|
|
6
|
-
export type DescriptionFn = (error: any) => ErrorDescription;
|
|
7
|
-
export declare class ErrorDescriptor {
|
|
8
|
-
private static descriptionFunction;
|
|
9
|
-
static setDescriptionFunction(fn: DescriptionFn): void;
|
|
10
|
-
static describe(error: any): ErrorDescription;
|
|
11
|
-
}
|
|
12
|
-
//# sourceMappingURL=ErrorDescriptor.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"ErrorDescriptor.d.ts","sourceRoot":"","sources":["../../../src/components/error/ErrorDescriptor.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,gBAAgB;IAC/B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAED,MAAM,MAAM,aAAa,GAAG,CAAC,KAAK,EAAE,GAAG,KAAK,gBAAgB,CAAA;AAE5D,qBAAa,eAAe;IAC1B,OAAO,CAAC,MAAM,CAAC,mBAAmB,CAEhC;IAEF,MAAM,CAAC,sBAAsB,CAAC,EAAE,EAAE,aAAa;IAI/C,MAAM,CAAC,QAAQ,CAAC,KAAK,EAAE,GAAG;CAG3B"}
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
export class ErrorDescriptor {
|
|
2
|
-
static setDescriptionFunction(fn) {
|
|
3
|
-
this.descriptionFunction = fn;
|
|
4
|
-
}
|
|
5
|
-
static describe(error) {
|
|
6
|
-
return this.descriptionFunction(error);
|
|
7
|
-
}
|
|
8
|
-
}
|
|
9
|
-
Object.defineProperty(ErrorDescriptor, "descriptionFunction", {
|
|
10
|
-
enumerable: true,
|
|
11
|
-
configurable: true,
|
|
12
|
-
writable: true,
|
|
13
|
-
value: error => ({
|
|
14
|
-
message: error.message || `${error}`,
|
|
15
|
-
})
|
|
16
|
-
});
|
|
17
|
-
//# sourceMappingURL=ErrorDescriptor.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"ErrorDescriptor.js","sourceRoot":"","sources":["../../../src/components/error/ErrorDescriptor.ts"],"names":[],"mappings":"AAQA,MAAM,OAAO,eAAe;IAK1B,MAAM,CAAC,sBAAsB,CAAC,EAAiB;QAC7C,IAAI,CAAC,mBAAmB,GAAG,EAAE,CAAA;IAC/B,CAAC;IAED,MAAM,CAAC,QAAQ,CAAC,KAAU;QACxB,OAAO,IAAI,CAAC,mBAAmB,CAAC,KAAK,CAAC,CAAA;IACxC,CAAC;;AAVc;;;;WAAqC,KAAK,CAAC,EAAE,CAAC,CAAC;QAC5D,OAAO,EAAE,KAAK,CAAC,OAAO,IAAI,GAAG,KAAK,EAAE;KACrC,CAAC;GAAA"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"useCheckTextOverflow.d.ts","sourceRoot":"","sources":["../../../src/components/menu/useCheckTextOverflow.tsx"],"names":[],"mappings":";AAEA,wBAAgB,oBAAoB;;;EAuBnC"}
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
import { useState, useRef, useEffect } from 'react';
|
|
2
|
-
export function useCheckTextOverflow() {
|
|
3
|
-
const [overflow, setOverflow] = useState(false);
|
|
4
|
-
const ref = useRef(null);
|
|
5
|
-
const checkOverflow = () => {
|
|
6
|
-
if (!ref.current) {
|
|
7
|
-
return;
|
|
8
|
-
}
|
|
9
|
-
const hasOverflow = ref.current.offsetWidth < ref.current.scrollWidth;
|
|
10
|
-
if (hasOverflow === overflow) {
|
|
11
|
-
return;
|
|
12
|
-
}
|
|
13
|
-
setOverflow(hasOverflow);
|
|
14
|
-
};
|
|
15
|
-
useEffect(() => {
|
|
16
|
-
checkOverflow();
|
|
17
|
-
}, [ref.current]);
|
|
18
|
-
return { overflow, ref };
|
|
19
|
-
}
|
|
20
|
-
//# sourceMappingURL=useCheckTextOverflow.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"useCheckTextOverflow.js","sourceRoot":"","sources":["../../../src/components/menu/useCheckTextOverflow.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAEpD,MAAM,UAAU,oBAAoB;IAClC,MAAM,CAAC,QAAQ,EAAE,WAAW,CAAC,GAAG,QAAQ,CAAU,KAAK,CAAC,CAAA;IACxD,MAAM,GAAG,GAAG,MAAM,CAAuB,IAAI,CAAC,CAAA;IAE9C,MAAM,aAAa,GAAG,GAAG,EAAE;QACzB,IAAI,CAAC,GAAG,CAAC,OAAO,EAAE,CAAC;YACjB,OAAM;QACR,CAAC;QAED,MAAM,WAAW,GAAG,GAAG,CAAC,OAAO,CAAC,WAAW,GAAG,GAAG,CAAC,OAAO,CAAC,WAAW,CAAA;QAErE,IAAI,WAAW,KAAK,QAAQ,EAAE,CAAC;YAC7B,OAAM;QACR,CAAC;QAED,WAAW,CAAC,WAAW,CAAC,CAAA;IAC1B,CAAC,CAAA;IAED,SAAS,CAAC,GAAG,EAAE;QACb,aAAa,EAAE,CAAA;IACjB,CAAC,EAAE,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAA;IAEjB,OAAO,EAAE,QAAQ,EAAE,GAAG,EAAE,CAAA;AAC1B,CAAC"}
|