@rpcbase/client 0.207.0 → 0.208.0-nav.0
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/helpers/useStoredValue/index.js +1 -1
- package/notifications/NotificationsSettingsModal/SettingsForm.js +1 -1
- package/package.json +1 -1
- package/ui/Tabs/{index.js → index.tsx} +2 -3
- package/ui/helpers/SizeContext/index.tsx +11 -0
- package/ui/helpers/useThrottledMeasure/index.js +1 -3
- package/ui/nav/ContentView/ContentViewContext.ts +22 -0
- package/ui/nav/ContentView/index.tsx +109 -0
- package/ui/nav/Header/header.scss +53 -0
- package/ui/nav/Header/index.tsx +150 -0
- package/ui/nav/Header/variables.scss +1 -0
- package/ui/nav/SidebarContainer/index.tsx +104 -0
- package/ui/nav/SidebarContainer/sidebar-container.scss +23 -0
- package/ui/nav/SlideoutContainer/components/Body.tsx +19 -0
- package/ui/nav/SlideoutContainer/components/Header.tsx +24 -0
- package/ui/nav/SlideoutContainer/components/Wrapper.tsx +46 -0
- package/ui/nav/SlideoutContainer/index.tsx +50 -0
- package/ui/nav/SlideoutContainer/slideout-container.scss +40 -0
- package/ui/nav/index.ts +4 -0
|
@@ -13,7 +13,7 @@ import setStoredValues from "./setStoredValues"
|
|
|
13
13
|
const DEFAULT_REMOTE = true
|
|
14
14
|
const UPDATE_THROTTLE_DELAY = 300
|
|
15
15
|
|
|
16
|
-
const useStoredValue = (_key, defaultValueOrFn = null, options = {}) => {
|
|
16
|
+
export const useStoredValue = (_key, defaultValueOrFn = null, options = {}) => {
|
|
17
17
|
const uid = getUid()
|
|
18
18
|
const key = `${uid}.${_key}`
|
|
19
19
|
|
package/package.json
CHANGED
|
@@ -1,16 +1,15 @@
|
|
|
1
|
-
/* @flow */
|
|
2
1
|
import React, {useState, useEffect, useRef} from "react"
|
|
3
2
|
import _isEmpty from "lodash/isEmpty"
|
|
4
3
|
|
|
5
4
|
import stopEventPropagation from "../helpers/stopEventPropagation"
|
|
6
|
-
import useThrottledMeasure from "..//helpers/useThrottledMeasure"
|
|
5
|
+
import {useThrottledMeasure} from "..//helpers/useThrottledMeasure"
|
|
7
6
|
|
|
8
7
|
import "./tabs.scss"
|
|
9
8
|
|
|
10
9
|
|
|
11
10
|
type Props = {
|
|
12
11
|
active: boolean,
|
|
13
|
-
onChange:
|
|
12
|
+
onChange: () => void,
|
|
14
13
|
scrollIntoView: boolean
|
|
15
14
|
}
|
|
16
15
|
|
|
@@ -6,7 +6,7 @@ import isEqual from "fast-deep-equal/react"
|
|
|
6
6
|
|
|
7
7
|
const DEFAULT_THROTTLE_TIME = 16
|
|
8
8
|
|
|
9
|
-
const useThrottledMeasure = (throttleDuration = DEFAULT_THROTTLE_TIME) => {
|
|
9
|
+
export const useThrottledMeasure = (throttleDuration = DEFAULT_THROTTLE_TIME) => {
|
|
10
10
|
const hasInitialMeasure = useRef(false)
|
|
11
11
|
|
|
12
12
|
const [ref, measuredRect] = useMeasure()
|
|
@@ -45,5 +45,3 @@ const useThrottledMeasure = (throttleDuration = DEFAULT_THROTTLE_TIME) => {
|
|
|
45
45
|
|
|
46
46
|
return [ref, rect]
|
|
47
47
|
}
|
|
48
|
-
|
|
49
|
-
export default useThrottledMeasure
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import {Dispatch, SetStateAction, createContext, useContext} from "react"
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
type ContentViewContextType = {
|
|
5
|
+
storageKeyPrefix: string;
|
|
6
|
+
isDocked: boolean;
|
|
7
|
+
setIsDocked: (value: boolean) => void;
|
|
8
|
+
sideItemViewWidth: number;
|
|
9
|
+
onUpdateSlideoutViewWidth: () => void;
|
|
10
|
+
contentViewWidth: number;
|
|
11
|
+
contentViewHeight: number;
|
|
12
|
+
sidebarWidth: number;
|
|
13
|
+
setSidebarWidth: Dispatch<SetStateAction<number>>;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export const ContentViewContext = createContext<ContentViewContextType>(
|
|
17
|
+
undefined!,
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
export default ContentViewContext
|
|
21
|
+
|
|
22
|
+
export const useContentViewContext = () => useContext(ContentViewContext)
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import {useState, memo, ReactNode, useMemo} from "react"
|
|
2
|
+
|
|
3
|
+
import {useStoredValue} from "../../../helpers/useStoredValue"
|
|
4
|
+
import {useThrottledMeasure} from "../../helpers/useThrottledMeasure"
|
|
5
|
+
|
|
6
|
+
import ContentViewContext from "./ContentViewContext"
|
|
7
|
+
import getUid from "../../../auth/getUid"
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
const DEFAULT_SIDEBAR_WIDTH = 200
|
|
11
|
+
|
|
12
|
+
const SIDEVIEW_WIDTH = "sideview_width"
|
|
13
|
+
|
|
14
|
+
export const ContentView = memo(
|
|
15
|
+
({
|
|
16
|
+
children,
|
|
17
|
+
storageKeyPrefix: _storageKeyPrefix,
|
|
18
|
+
}: {
|
|
19
|
+
children: ReactNode;
|
|
20
|
+
storageKeyPrefix?: string;
|
|
21
|
+
}) => {
|
|
22
|
+
const uid = getUid()
|
|
23
|
+
|
|
24
|
+
const [isDocked, setIsDocked] = useState(false)
|
|
25
|
+
|
|
26
|
+
const [
|
|
27
|
+
contentViewRef,
|
|
28
|
+
{width: contentViewWidth, height: contentViewHeight},
|
|
29
|
+
] = useThrottledMeasure()
|
|
30
|
+
|
|
31
|
+
const storageKeyPrefix = _storageKeyPrefix
|
|
32
|
+
? _storageKeyPrefix
|
|
33
|
+
: uid || "unknown"
|
|
34
|
+
|
|
35
|
+
const sidebarWidthKey = `${storageKeyPrefix}.sidebar_width`
|
|
36
|
+
const [sidebarWidth, setSidebarWidth] = useStoredValue(
|
|
37
|
+
sidebarWidthKey,
|
|
38
|
+
DEFAULT_SIDEBAR_WIDTH,
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
const getKey = (k) => `${storageKeyPrefix}.content_view.${k}`
|
|
42
|
+
|
|
43
|
+
const [sideItemViewWidth, setSlideoutViewWidth] = useStoredValue(
|
|
44
|
+
getKey(SIDEVIEW_WIDTH),
|
|
45
|
+
320,
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
const onUpdateSlideoutViewWidth = (width) => {
|
|
49
|
+
setSlideoutViewWidth(width)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const contentWidthOffset = useMemo(() => {
|
|
53
|
+
if (isDocked) {
|
|
54
|
+
return sidebarWidth + sideItemViewWidth
|
|
55
|
+
} else {
|
|
56
|
+
return sidebarWidth
|
|
57
|
+
}
|
|
58
|
+
}, [isDocked, sidebarWidth, sideItemViewWidth])
|
|
59
|
+
|
|
60
|
+
// don't render anything before values are initialized
|
|
61
|
+
if (
|
|
62
|
+
typeof sidebarWidth === "undefined" ||
|
|
63
|
+
typeof sideItemViewWidth === "undefined"
|
|
64
|
+
)
|
|
65
|
+
return null
|
|
66
|
+
|
|
67
|
+
return (
|
|
68
|
+
<ContentViewContext
|
|
69
|
+
value={{
|
|
70
|
+
storageKeyPrefix,
|
|
71
|
+
isDocked,
|
|
72
|
+
setIsDocked,
|
|
73
|
+
sideItemViewWidth,
|
|
74
|
+
onUpdateSlideoutViewWidth,
|
|
75
|
+
contentViewWidth,
|
|
76
|
+
contentViewHeight,
|
|
77
|
+
sidebarWidth,
|
|
78
|
+
setSidebarWidth,
|
|
79
|
+
}}
|
|
80
|
+
>
|
|
81
|
+
<div
|
|
82
|
+
className=""
|
|
83
|
+
style={{
|
|
84
|
+
display: "flex",
|
|
85
|
+
flexDirection: "row",
|
|
86
|
+
}}
|
|
87
|
+
>
|
|
88
|
+
<div id="sidebar-container" />
|
|
89
|
+
|
|
90
|
+
<div
|
|
91
|
+
ref={contentViewRef}
|
|
92
|
+
key={`content-wrapper-${"content_view_"}`}
|
|
93
|
+
style={{
|
|
94
|
+
width: `calc(100vw - ${contentWidthOffset}px)`,
|
|
95
|
+
height: "calc(100vh - 45px)",
|
|
96
|
+
marginTop: 45,
|
|
97
|
+
overflowY: "scroll",
|
|
98
|
+
marginLeft: sidebarWidth,
|
|
99
|
+
}}
|
|
100
|
+
>
|
|
101
|
+
{children}
|
|
102
|
+
</div>
|
|
103
|
+
</div>
|
|
104
|
+
|
|
105
|
+
<div id="slideout-container" />
|
|
106
|
+
</ContentViewContext>
|
|
107
|
+
)
|
|
108
|
+
},
|
|
109
|
+
)
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
@import "helpers";
|
|
2
|
+
|
|
3
|
+
@import "./variables";
|
|
4
|
+
|
|
5
|
+
$brand-v-padding: 11px;
|
|
6
|
+
$account-max-width: 160px;
|
|
7
|
+
$img-size: 32px;
|
|
8
|
+
$img-border-radius: 3px;
|
|
9
|
+
|
|
10
|
+
#main-header-nav {
|
|
11
|
+
border-bottom: 1px solid $black;
|
|
12
|
+
background-color: $black !important;
|
|
13
|
+
height: 45px;
|
|
14
|
+
|
|
15
|
+
.nav-link {
|
|
16
|
+
color: $gray-200;
|
|
17
|
+
|
|
18
|
+
&.active {
|
|
19
|
+
font-weight: bold;
|
|
20
|
+
color: $white;
|
|
21
|
+
text-shadow: lighten(rgba($blue, 0.6), 80%) 1px 0 8px;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
.current-account-subtitle {
|
|
27
|
+
color: $gray-900;
|
|
28
|
+
max-width: $account-max-width;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
.accounts-list-item {
|
|
32
|
+
display: flex !important;
|
|
33
|
+
flex-direction: row !important;
|
|
34
|
+
align-items: center;
|
|
35
|
+
|
|
36
|
+
.text-truncate {
|
|
37
|
+
max-width: $account-max-width;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
img {
|
|
41
|
+
width: $img-size;
|
|
42
|
+
height: $img-size;
|
|
43
|
+
border-radius: $img-border-radius;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
.account-info {
|
|
47
|
+
max-width: 130px;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
.account-info-small {
|
|
51
|
+
color: $gray-600;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
/* eslint-disable */
|
|
2
|
+
import assert from "assert"
|
|
3
|
+
import {useState, useEffect} from "react"
|
|
4
|
+
|
|
5
|
+
import apiClient from "../../../apiClient"
|
|
6
|
+
|
|
7
|
+
// import SearchAnything from "components/search/SearchAnything"
|
|
8
|
+
|
|
9
|
+
// import LogoNav from "./components/LogoNav"
|
|
10
|
+
|
|
11
|
+
// Env Selector
|
|
12
|
+
// import useFilteredEnvs from "./components/EnvSelector/useFilteredEnvs"
|
|
13
|
+
// import EnvSelectorToggle from "./components/EnvSelector/Toggle"
|
|
14
|
+
// import EnvSelectorDropdown from "./components/EnvSelector/Dropdown"
|
|
15
|
+
// Group Selector
|
|
16
|
+
// import useFilteredGroups from "./components/GroupSelector/useFilteredGroups"
|
|
17
|
+
// import GroupSelectorToggle from "./components/GroupSelector/Toggle"
|
|
18
|
+
// import GroupSelectorDropdown from "./components/GroupSelector/Dropdown"
|
|
19
|
+
|
|
20
|
+
// import MorphingDropdown from "./components/MorphingDropdown"
|
|
21
|
+
|
|
22
|
+
// import AccountsToggle from "./components/AccountsToggle"
|
|
23
|
+
// import NotificationsToggle from "./components/NotificationsToggle"
|
|
24
|
+
// import EnvSettingsToggle from "./components/EnvSettingsToggle"
|
|
25
|
+
// import PublishControl from "./components/PublishControl"
|
|
26
|
+
|
|
27
|
+
// import AccountsDropdown from "./components/AccountsDropdown"
|
|
28
|
+
// import NotificationsDropdown from "./components/NotificationsDropdown"
|
|
29
|
+
// import PhoneDropdown from "components/phone/PhoneDropdown"
|
|
30
|
+
// import EnvSettingsDropdown from "./components/EnvSettingsDropdown"
|
|
31
|
+
|
|
32
|
+
import "./header.scss"
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
const DefaultHeader = () => {
|
|
36
|
+
return (
|
|
37
|
+
<>
|
|
38
|
+
<LogoNav isSignedIn={false} />
|
|
39
|
+
|
|
40
|
+
<div className="nav-default d-flex flex-row justify-content-between w-100">
|
|
41
|
+
<div className="d-flex flex-row"></div>
|
|
42
|
+
|
|
43
|
+
<div className="d-flex flex-row">
|
|
44
|
+
<a className="nav-link px-2 me-2 fw-bold" href="/signin">
|
|
45
|
+
Sign In
|
|
46
|
+
</a>
|
|
47
|
+
</div>
|
|
48
|
+
</div>
|
|
49
|
+
</>
|
|
50
|
+
)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const SignedInHeader = () => {
|
|
54
|
+
const loc = window.location.pathname
|
|
55
|
+
|
|
56
|
+
const [accounts, setAccounts] = useState([])
|
|
57
|
+
|
|
58
|
+
const {envs, activeEnv, setFilter: setEnvsFilter} = useFilteredEnvs()
|
|
59
|
+
const {groups, activeGroup, setFilter: setGroupsFilter} = useFilteredGroups()
|
|
60
|
+
|
|
61
|
+
useEffect(() => {
|
|
62
|
+
// TODO: useapi hook with cache
|
|
63
|
+
const load = async() => {
|
|
64
|
+
const res = await apiClient.post("/api/v1/auth/get_accounts")
|
|
65
|
+
assert(res.data.status === "ok")
|
|
66
|
+
setAccounts(res.data.accounts)
|
|
67
|
+
// console.log("accounts", res.data.accounts)
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
load()
|
|
71
|
+
}, [])
|
|
72
|
+
|
|
73
|
+
return (
|
|
74
|
+
<>
|
|
75
|
+
<MorphingDropdown.Provider side="left">
|
|
76
|
+
<LogoNav isSignedIn={true} />
|
|
77
|
+
|
|
78
|
+
<EnvSelectorToggle activeEnv={activeEnv} />
|
|
79
|
+
<GroupSelectorToggle activeGroup={activeGroup} />
|
|
80
|
+
|
|
81
|
+
<MorphingDropdown.Portal>
|
|
82
|
+
<EnvSelectorDropdown
|
|
83
|
+
id="env-selector"
|
|
84
|
+
envs={envs}
|
|
85
|
+
activeEnv={activeEnv}
|
|
86
|
+
setFilter={setEnvsFilter}
|
|
87
|
+
/>
|
|
88
|
+
|
|
89
|
+
<GroupSelectorDropdown
|
|
90
|
+
id="group-selector"
|
|
91
|
+
groups={groups}
|
|
92
|
+
activeGroup={activeGroup}
|
|
93
|
+
setFilter={setGroupsFilter}
|
|
94
|
+
/>
|
|
95
|
+
</MorphingDropdown.Portal>
|
|
96
|
+
</MorphingDropdown.Provider>
|
|
97
|
+
|
|
98
|
+
{/* Search anything */}
|
|
99
|
+
<SearchAnything />
|
|
100
|
+
|
|
101
|
+
<div className="ms-auto d-none d-md-flex flex-row text-truncate">
|
|
102
|
+
<a className="nav-link mx-2 px-1" href="/docs">
|
|
103
|
+
Docs
|
|
104
|
+
</a>
|
|
105
|
+
<a
|
|
106
|
+
className={cx("nav-link px-1", {active: loc.startsWith("/marketplace")})}
|
|
107
|
+
href="/marketplace"
|
|
108
|
+
>
|
|
109
|
+
Marketplace
|
|
110
|
+
</a>
|
|
111
|
+
</div>
|
|
112
|
+
|
|
113
|
+
<PublishControl />
|
|
114
|
+
|
|
115
|
+
{/* Phone */}
|
|
116
|
+
<PhoneDropdown />
|
|
117
|
+
|
|
118
|
+
<MorphingDropdown.Provider side="right">
|
|
119
|
+
{/* WARNING: update the anchor-${ids} on the toggle to match the menus if adding or removing */}
|
|
120
|
+
<NotificationsToggle />
|
|
121
|
+
|
|
122
|
+
<EnvSettingsToggle />
|
|
123
|
+
|
|
124
|
+
<AccountsToggle />
|
|
125
|
+
|
|
126
|
+
<MorphingDropdown.Portal>
|
|
127
|
+
{/* Notifications */}
|
|
128
|
+
<NotificationsDropdown id="notifications" />
|
|
129
|
+
|
|
130
|
+
{/* Env Settings */}
|
|
131
|
+
<EnvSettingsDropdown id="env-setup" />
|
|
132
|
+
|
|
133
|
+
{/* Accounts + Org */}
|
|
134
|
+
<AccountsDropdown id="accounts" accounts={accounts} />
|
|
135
|
+
</MorphingDropdown.Portal>
|
|
136
|
+
</MorphingDropdown.Provider>
|
|
137
|
+
</>
|
|
138
|
+
)
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export const Header = ({isSignedIn}) => {
|
|
142
|
+
return (
|
|
143
|
+
<nav
|
|
144
|
+
id="main-header-nav"
|
|
145
|
+
className={"d-flex align-items-center fixed-top bg-dark flex-md-nowrap shadow p-0 text-light"}
|
|
146
|
+
>
|
|
147
|
+
this is my header yo
|
|
148
|
+
</nav>
|
|
149
|
+
)
|
|
150
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
$header-height: 44px;
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import {useState, useRef} from "react"
|
|
2
|
+
import {createPortal} from "react-dom"
|
|
3
|
+
import {ResizableBox} from "react-resizable"
|
|
4
|
+
|
|
5
|
+
import {useStoredValue} from "../../../helpers/useStoredValue"
|
|
6
|
+
import {useContentViewContext} from "../ContentView/ContentViewContext"
|
|
7
|
+
|
|
8
|
+
import "./sidebar-container.scss"
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
const COLLAPSED_MIN_WIDTH = 40
|
|
12
|
+
const OPENED_MIN_WIDTH = 60
|
|
13
|
+
|
|
14
|
+
export const SidebarContainer = ({children}) => {
|
|
15
|
+
const {sidebarWidth, setSidebarWidth, storageKeyPrefix} = useContentViewContext()
|
|
16
|
+
|
|
17
|
+
const previousWidthRef = useRef(sidebarWidth)
|
|
18
|
+
|
|
19
|
+
// TODO: store this ?? (no we already store the sidebarWidth)
|
|
20
|
+
const [isCollapsed, setIsCollapsed] = useState(
|
|
21
|
+
sidebarWidth === COLLAPSED_MIN_WIDTH,
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
const storageKey = `${storageKeyPrefix}.sidebar_uncollapsed_width`
|
|
25
|
+
|
|
26
|
+
const [uncollapsedSidebarWidth, setUncollapsedSidebarWidth] = useStoredValue(
|
|
27
|
+
storageKey,
|
|
28
|
+
sidebarWidth,
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
const onResize = (e, data) => {
|
|
33
|
+
const width = data.size.width
|
|
34
|
+
|
|
35
|
+
const delta = previousWidthRef.current - width
|
|
36
|
+
|
|
37
|
+
let direction
|
|
38
|
+
|
|
39
|
+
if (delta < 0) direction = "right"
|
|
40
|
+
else if (delta > 0) direction = "left"
|
|
41
|
+
|
|
42
|
+
// sidebar opening
|
|
43
|
+
if (width > COLLAPSED_MIN_WIDTH && direction === "right" && isCollapsed) {
|
|
44
|
+
setSidebarWidth(OPENED_MIN_WIDTH)
|
|
45
|
+
setIsCollapsed(false)
|
|
46
|
+
}
|
|
47
|
+
// collapsing
|
|
48
|
+
else if (
|
|
49
|
+
width <= OPENED_MIN_WIDTH &&
|
|
50
|
+
direction === "left" &&
|
|
51
|
+
!isCollapsed
|
|
52
|
+
) {
|
|
53
|
+
setSidebarWidth(COLLAPSED_MIN_WIDTH)
|
|
54
|
+
setIsCollapsed(true)
|
|
55
|
+
}
|
|
56
|
+
// normal case, but only apply new width if different from previous one
|
|
57
|
+
else if (width !== sidebarWidth) {
|
|
58
|
+
setSidebarWidth(width)
|
|
59
|
+
setUncollapsedSidebarWidth(width)
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// save width to compare on next event
|
|
63
|
+
previousWidthRef.current = width
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// quick collapse by double clicking on sidebar resize bar
|
|
67
|
+
const onDoubleClickResizeHandle = () => {
|
|
68
|
+
if (isCollapsed) {
|
|
69
|
+
setSidebarWidth(uncollapsedSidebarWidth)
|
|
70
|
+
setIsCollapsed(false)
|
|
71
|
+
} else {
|
|
72
|
+
setSidebarWidth(COLLAPSED_MIN_WIDTH)
|
|
73
|
+
setIsCollapsed(true)
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const containerElement = document.getElementById("sidebar-container")
|
|
78
|
+
|
|
79
|
+
if (!containerElement) return null
|
|
80
|
+
|
|
81
|
+
return createPortal((
|
|
82
|
+
<ResizableBox
|
|
83
|
+
id="sidebar"
|
|
84
|
+
width={sidebarWidth}
|
|
85
|
+
// height={height}
|
|
86
|
+
// style={{ position: "fixed", top: 45, bottom: 0, overflowX: "visible" }}
|
|
87
|
+
draggableOpts={{}}
|
|
88
|
+
handle={
|
|
89
|
+
<div
|
|
90
|
+
className="sidebar-resize-handle"
|
|
91
|
+
onDoubleClick={onDoubleClickResizeHandle}
|
|
92
|
+
/>
|
|
93
|
+
}
|
|
94
|
+
resizeHandles={["e"]}
|
|
95
|
+
axis="x"
|
|
96
|
+
minConstraints={[isCollapsed ? COLLAPSED_MIN_WIDTH : OPENED_MIN_WIDTH]}
|
|
97
|
+
maxConstraints={[600]}
|
|
98
|
+
onResize={onResize}
|
|
99
|
+
style={{overflow: "hidden"}}
|
|
100
|
+
>
|
|
101
|
+
{children}
|
|
102
|
+
</ResizableBox>
|
|
103
|
+
), containerElement)
|
|
104
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
@import "helpers";
|
|
2
|
+
|
|
3
|
+
#sidebar-container {
|
|
4
|
+
position: fixed;
|
|
5
|
+
top: 45px;
|
|
6
|
+
left: 0;
|
|
7
|
+
/* background-color: red; */
|
|
8
|
+
max-height: calc(100vh - 45px);
|
|
9
|
+
|
|
10
|
+
.sidebar-resize-handle {
|
|
11
|
+
z-index: 3;
|
|
12
|
+
|
|
13
|
+
// transform: translateX(2px);
|
|
14
|
+
position: absolute;
|
|
15
|
+
right: -2px;
|
|
16
|
+
top: 0;
|
|
17
|
+
display: block;
|
|
18
|
+
height: 100%;
|
|
19
|
+
width: 4px;
|
|
20
|
+
cursor: ew-resize;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import {useContext} from "react"
|
|
2
|
+
import Offcanvas from "react-bootstrap/Offcanvas"
|
|
3
|
+
|
|
4
|
+
import ContentViewContext from "../../ContentView/ContentViewContext"
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
export const Body = ({children}) => {
|
|
8
|
+
const contentViewContext = useContext(ContentViewContext)
|
|
9
|
+
|
|
10
|
+
if (contentViewContext.isDocked) {
|
|
11
|
+
return children
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
return (
|
|
15
|
+
<Offcanvas.Body className="d-flex flex-column px-0 pb-0">
|
|
16
|
+
{children}
|
|
17
|
+
</Offcanvas.Body>
|
|
18
|
+
)
|
|
19
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/* @flow */
|
|
2
|
+
import {useContext} from "react"
|
|
3
|
+
import Offcanvas from "react-bootstrap/Offcanvas"
|
|
4
|
+
|
|
5
|
+
import {ContentViewContext} from "../../ContentView/ContentViewContext"
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
export const Header = ({children}) => {
|
|
9
|
+
const contentViewContext = useContext(ContentViewContext)
|
|
10
|
+
|
|
11
|
+
if (contentViewContext.isDocked) {
|
|
12
|
+
return (
|
|
13
|
+
<div className="bg-light" style={{height: 41}}>
|
|
14
|
+
{children}
|
|
15
|
+
</div>
|
|
16
|
+
)
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
return (
|
|
20
|
+
<Offcanvas.Header closeButton>
|
|
21
|
+
<Offcanvas.Title>{children}</Offcanvas.Title>
|
|
22
|
+
</Offcanvas.Header>
|
|
23
|
+
)
|
|
24
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import Offcanvas from "react-bootstrap/Offcanvas"
|
|
2
|
+
import {ResizableBox} from "react-resizable"
|
|
3
|
+
|
|
4
|
+
import {useContentViewContext} from "../../ContentView/ContentViewContext"
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
export const Wrapper = ({onUpdateWidth, show, onHide, children}) => {
|
|
8
|
+
const contentViewContext = useContentViewContext()
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
const onResize = (e, data) => {
|
|
12
|
+
const val = data.size.width
|
|
13
|
+
onUpdateWidth(val)
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
if (contentViewContext.isDocked) {
|
|
17
|
+
return (
|
|
18
|
+
<ResizableBox
|
|
19
|
+
id="slideout-view-wrapper"
|
|
20
|
+
width={contentViewContext.sideItemViewWidth}
|
|
21
|
+
style={{position: "fixed", top: 45, right: 0, bottom: 0, overflowX: "visible"}}
|
|
22
|
+
draggableOpts={{}}
|
|
23
|
+
handle={<div className="slideout-view-resize-handle" />}
|
|
24
|
+
resizeHandles={["w"]}
|
|
25
|
+
axis="x"
|
|
26
|
+
minConstraints={[120]}
|
|
27
|
+
maxConstraints={[768]}
|
|
28
|
+
onResize={onResize}
|
|
29
|
+
>
|
|
30
|
+
<div className="h-100">{children}</div>
|
|
31
|
+
</ResizableBox>
|
|
32
|
+
)
|
|
33
|
+
} else {
|
|
34
|
+
return (
|
|
35
|
+
<Offcanvas
|
|
36
|
+
className="slideout-view-offcanvas"
|
|
37
|
+
placement="end"
|
|
38
|
+
backdropClassName="slideout-view-backdrop"
|
|
39
|
+
show={show}
|
|
40
|
+
onHide={onHide}
|
|
41
|
+
>
|
|
42
|
+
{children}
|
|
43
|
+
</Offcanvas>
|
|
44
|
+
)
|
|
45
|
+
}
|
|
46
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import {memo, ReactNode, useEffect} from "react"
|
|
2
|
+
import {createPortal} from "react-dom"
|
|
3
|
+
|
|
4
|
+
import {useContentViewContext} from "../ContentView/ContentViewContext"
|
|
5
|
+
|
|
6
|
+
import {Wrapper} from "./components/Wrapper"
|
|
7
|
+
import {Header} from "./components/Header"
|
|
8
|
+
import {Body} from "./components/Body"
|
|
9
|
+
|
|
10
|
+
import "./slideout-container.scss"
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
export const SlideoutContainer = memo(
|
|
14
|
+
({
|
|
15
|
+
children,
|
|
16
|
+
onHide,
|
|
17
|
+
isDocked = false,
|
|
18
|
+
header,
|
|
19
|
+
}: {
|
|
20
|
+
children: ReactNode;
|
|
21
|
+
onHide?: () => void;
|
|
22
|
+
isDocked?: boolean;
|
|
23
|
+
header?: ReactNode | string;
|
|
24
|
+
}) => {
|
|
25
|
+
const contentViewContext = useContentViewContext()
|
|
26
|
+
|
|
27
|
+
const {onUpdateSlideoutViewWidth: onUpdateWidth} = contentViewContext
|
|
28
|
+
|
|
29
|
+
useEffect(() => {
|
|
30
|
+
contentViewContext.setIsDocked(isDocked)
|
|
31
|
+
|
|
32
|
+
return () => contentViewContext.setIsDocked(false)
|
|
33
|
+
}, [isDocked])
|
|
34
|
+
|
|
35
|
+
// if (!contentViewContext.isDocked) return null
|
|
36
|
+
// if (contentViewContext.isCollapsed && contentViewContext.isDocked) return null
|
|
37
|
+
|
|
38
|
+
const containerElement = document.getElementById("slideout-container")
|
|
39
|
+
if (!containerElement) return null
|
|
40
|
+
|
|
41
|
+
return createPortal(
|
|
42
|
+
<Wrapper onUpdateWidth={onUpdateWidth} show={true} onHide={onHide}>
|
|
43
|
+
{header && <Header>{header}</Header>}
|
|
44
|
+
|
|
45
|
+
<Body>{children}</Body>
|
|
46
|
+
</Wrapper>,
|
|
47
|
+
containerElement,
|
|
48
|
+
)
|
|
49
|
+
},
|
|
50
|
+
)
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
@import "helpers";
|
|
2
|
+
|
|
3
|
+
#slideout-view-wrapper {
|
|
4
|
+
border-left: 1px solid $border-color;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
.slideout-view-resize-handle {
|
|
9
|
+
z-index: 3;
|
|
10
|
+
|
|
11
|
+
position: absolute;
|
|
12
|
+
left: -2px;
|
|
13
|
+
top: 0;
|
|
14
|
+
display: block;
|
|
15
|
+
height: 100%;
|
|
16
|
+
width: 4px;
|
|
17
|
+
cursor: ew-resize;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
.slideout-view-offcanvas.offcanvas {
|
|
21
|
+
transition: none;
|
|
22
|
+
|
|
23
|
+
.offcanvas-header {
|
|
24
|
+
height: 45px;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
.offcanvas-title {
|
|
28
|
+
font-size: 1rem;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
.offcanvas-body {
|
|
32
|
+
border-top: 1px solid $border-color;
|
|
33
|
+
font-size: $font-size-base;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
.slideout-view-backdrop.offcanvas-backdrop {
|
|
38
|
+
transition-duration: 80ms;
|
|
39
|
+
color: $red-800;
|
|
40
|
+
}
|
package/ui/nav/index.ts
ADDED