-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathNavigationPanel.tsx
More file actions
179 lines (163 loc) · 6.63 KB
/
NavigationPanel.tsx
File metadata and controls
179 lines (163 loc) · 6.63 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
/*---------------------------------------------------------------------------------------------
* Copyright (c) Bentley Systems, Incorporated. All rights reserved.
* See LICENSE.md in the project root for license terms and full copyright notice.
*--------------------------------------------------------------------------------------------*/
import * as React from "react";
import classnames from "classnames";
import { IconSpec } from "@itwin/core-react";
import { ConditionalBooleanValue, ConditionalStringValue } from "@itwin/appui-abstract";
import { IconImage } from "./IconImage";
import { ClassNameProps, CommonProps, useSyncUiEvent } from "./MobileUi";
import "./NavigationPanel.scss";
import {
CloseCircle as CloseCircleSvg,
Close as CloseSvg,
MinimizeCircle as MinimizeCircleSvg,
Minimize as MinimizeSvg,
} from "./images-tsx";
/**
* Properties for the {@link NavigationPanel} component.
* @public
*/
export interface NavigationPanelProps extends ClassNameProps {
/** The left side components. */
left?: React.ReactNode;
/** The right side components. */
right?: React.ReactNode;
}
/**
* A React component representing a horizontal container of {@link NavigationButton} components.
* @public
*/
export function NavigationPanel(props: NavigationPanelProps) {
return <div className={classnames("mui-navigation-panel", props.className)}>
<div className="mui-navigation-gradient" />
{<div className="mui-navigation-panel-section">{props.left}</div>}
{<div className="mui-navigation-panel-section">{props.right}</div>}
</div>;
}
/**
* Properties for the {@link NavigationButton} component.
* @public
*/
export interface NavigationButtonProps extends CommonProps {
/** The icon. */
iconSpec: IconSpec;
/** The button size, default is "42px". */
size?: string;
/** The width, default is size if specified, "42px" otherwise. */
width?: string;
/** The height, default is size if specified, "42px" otherwise. */
height?: string;
/** The icon size, default is "24px". */
iconSize?: string;
/** The enabled/disabled state, default is true. */
enabled?: boolean;
/** The icon color, default is muic-foreground-navigation-button. */
color?: string;
/** The icon stroke width, default is 2px. */
strokeWidth?: string;
/** If set to true, disables the shadow, default is false. */
noShadow?: boolean;
/** The click handler. */
onClick?: (e: React.MouseEvent) => void;
// onTouchStart?: (e: React.TouchEvent) => void;
}
/**
* A React component representing a clickable button, usually placed in the {@link NavigationPanel}.
* @public
*/
export function NavigationButton(props: NavigationButtonProps) {
const { className, color, strokeWidth, noShadow = false, style, enabled = true, size, width, height } = props;
return <div
className={classnames("mui-navigation-button", !enabled && "disabled", noShadow && "no-shadow", className)}
style={{ width: width ?? size ?? "42px", height: height ?? size ?? "42px", color, strokeWidth, ...style }}
// Note: The below commented out onTouchStart will be needed if we add :active tracking. For some
// unknown reason, it fixes an iOS Safari glitch when tracking the :active state of a DOM element.
// onTouchStart={props.onTouchStart ? props.onTouchStart : () => { }}
onClick={enabled ? (e) => {
e.stopPropagation();
props.onClick && props.onClick(e);
} : undefined}>
<IconImage iconSpec={props.iconSpec} size={props.iconSize || "24px"} />
</div>;
}
/**
* A {@link NavigationButton} that uses the "X" (close) icon, intended for use with nested frontstages.
* @public
*/
export function CloseButton(props: Omit<NavigationButtonProps, "iconSpec">) {
const { strokeWidth = "2px", ...otherProps } = props;
return <NavigationButton strokeWidth={strokeWidth} iconSpec={<CloseSvg />} {...otherProps} />;
}
/**
* A {@link NavigationButton} that uses the "v" (down chevron) icon.
* @public
*/
export function MinimizeButton(props: Omit<NavigationButtonProps, "iconSpec">) {
const { strokeWidth = "3px", ...otherProps } = props;
return <NavigationButton strokeWidth={strokeWidth} iconSpec={<MinimizeSvg />} {...otherProps} />;
}
/**
* A {@link NavigationButton} that uses the "v" (down chevron) inside a filled circle icon.
* @public
*/
export function CircularMinimizeButton(props: Omit<NavigationButtonProps, "iconSpec">) {
const { strokeWidth = "2px", ...otherProps } = props;
return <NavigationButton strokeWidth={strokeWidth} iconSpec={<MinimizeCircleSvg />} {...otherProps} />;
}
/**
* A {@link NavigationButton} that uses the "X" (diagonal cross) inside a filled circle icon.
* @public
*/
export function CircularCloseButton(props: Omit<NavigationButtonProps, "iconSpec">) {
const { strokeWidth = "2px", ...otherProps } = props;
return <NavigationButton strokeWidth={strokeWidth} iconSpec={<CloseCircleSvg />} {...otherProps} />;
}
/**
* A {@link NavigationButton} that has no shadow and has a foreground color that is black in light mode and white in dark mode.
* @public
*/
export function ToolButton(props: Omit<NavigationButtonProps, "color" | "noShadow">) {
return <NavigationButton {...props} color="var(--muic-foreground)" noShadow />;
}
/**
* Properties for the {@link ConditionalNavigationButton} component.
* @public
*/
export interface ConditionalNavigationButtonProps extends NavigationButtonProps {
/** Controls if the button is displayed, default true. */
isVisible?: ConditionalBooleanValue | boolean;
}
/**
* A React component composing a {@link NavigationButton} with conditional visibility and icon specification.
* @public
*/
export function ConditionalNavigationButton(props: ConditionalNavigationButtonProps) {
const { isVisible = true, iconSpec, ...others } = props;
const [displayed, setDisplayed] = React.useState<boolean>();
const [localIconSpec, setLocalIconSpec] = React.useState<IconSpec>();
React.useEffect(() => {
if (isVisible instanceof ConditionalBooleanValue) {
isVisible.refresh();
}
setDisplayed(ConditionalBooleanValue.getValue(isVisible));
if (iconSpec instanceof ConditionalStringValue) {
iconSpec.refresh();
setLocalIconSpec(iconSpec.value);
} else {
setLocalIconSpec(iconSpec);
}
}, [isVisible, iconSpec]);
useSyncUiEvent((args) => {
if (isVisible instanceof ConditionalBooleanValue && ConditionalBooleanValue.refreshValue(isVisible, args.eventIds)) {
setDisplayed(isVisible.value);
}
if (iconSpec instanceof ConditionalStringValue && ConditionalStringValue.refreshValue(iconSpec, args.eventIds)) {
setLocalIconSpec(iconSpec.value);
}
});
return <>
{displayed && <NavigationButton iconSpec={localIconSpec} {...others} />}
</>;
}