Skip to content

Commit 7fe7117

Browse files
georginahalpernGeorgina
andauthored
[Fluent] Update NME to use fluent accordion for both nodeList and properties pane (#16847)
- Update nodeListComponent to conditionally use accordion - Update NME propertyTabComponents to use fluent accordion. This change required a bit of refactoring because the fluent accordion requires its first children to be the accordionItems, so in cases where we had wrappers around the LineContainer we would break the accordion. I thus had to push the accordion inside the individual fooPropertyTabComponent classes and in certain cases (ex: GenericProperties, InputProperties) where the properties were added dynamically, expose them via helper functions) - Minor styling changes --------- Co-authored-by: Georgina <[email protected]>
1 parent 69c3778 commit 7fe7117

28 files changed

+666
-510
lines changed

packages/dev/inspector-v2/src/components/accordionPane.tsx

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -62,11 +62,6 @@ const useStyles = makeStyles({
6262
display: "flex",
6363
flexDirection: "column",
6464
},
65-
panelDiv: {
66-
display: "flex",
67-
flexDirection: "column",
68-
overflow: "hidden",
69-
},
7065
});
7166

7267
export type AccordionPaneSectionProps = {

packages/dev/inspector/src/components/actionTabs/tabs/propertyGridTabComponent.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -775,7 +775,7 @@ export class PropertyGridTabComponent extends PaneComponent {
775775
const entity = this.props.selectedEntity || {};
776776
const entityHasMetadataProp = Object.prototype.hasOwnProperty.call(entity, "metadata");
777777
return (
778-
<FluentToolWrapper>
778+
<FluentToolWrapper toolName="INSPECTOR">
779779
<div className="pane">
780780
{this.renderContent()}
781781
{Tags.HasTags(entity) && (
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import { useContext } from "react";
2+
import type { FunctionComponent, PropsWithChildren } from "react";
3+
import { ToolContext } from "../fluent/hoc/fluentToolWrapper";
4+
import { Pane } from "../fluent/hoc/pane";
5+
import { Accordion } from "../fluent/primitives/accordion";
6+
7+
/**
8+
* A wrapper component for the property tab that provides a consistent layout and styling.
9+
* It uses a Pane and an Accordion to organize the content, so its direct children
10+
* must have 'title' props to be compatible with the Accordion structure.
11+
* @param props The props to pass to the component.
12+
* @returns The rendered component.
13+
*/
14+
export const PropertyTabComponentBase: FunctionComponent<PropsWithChildren> = (props) => {
15+
const context = useContext(ToolContext);
16+
const fluentWrapper: FunctionComponent<PropsWithChildren> = (props) => {
17+
return (
18+
<Pane title={context.toolName}>
19+
<Accordion>{props.children}</Accordion>
20+
</Pane>
21+
);
22+
};
23+
const originalWrapper: FunctionComponent<PropsWithChildren> = (props) => {
24+
return (
25+
<div id="propertyTab">
26+
<div id="header">
27+
<img id="logo" src="https://www.babylonjs.com/Assets/logo-babylonjs-social-twitter.png" />
28+
<div id="title">{context.toolName}</div>
29+
</div>
30+
<div>{props.children}</div>
31+
</div>
32+
);
33+
};
34+
// eslint-disable-next-line @typescript-eslint/naming-convention
35+
const Wrapper = context.useFluent ? fluentWrapper : originalWrapper;
36+
return <Wrapper>{props.children}</Wrapper>;
37+
};

packages/dev/sharedUiComponents/src/fluent/hoc/fluentToolWrapper.tsx

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,14 @@ export type ToolHostProps = {
1313
* Can be set to true to disable the copy button in the tool's property lines. Default is false (copy enabled)
1414
*/
1515
disableCopy?: boolean;
16+
17+
/**
18+
* Name of the tool displayed in the UX
19+
*/
20+
toolName: string;
1621
};
1722

18-
export const ToolContext = createContext({ useFluent: false as boolean, disableCopy: false as boolean } as const);
23+
export const ToolContext = createContext({ useFluent: false as boolean, disableCopy: false as boolean, toolName: "" as string } as const);
1924

2025
/**
2126
* For tools which are ready to move over the fluent, wrap the root of the tool (or the panel which you want fluentized) with this component
@@ -25,13 +30,17 @@ export const ToolContext = createContext({ useFluent: false as boolean, disableC
2530
*/
2631
export const FluentToolWrapper: FunctionComponent<PropsWithChildren<ToolHostProps>> = (props) => {
2732
const url = new URL(window.location.href);
28-
const enableFluent = url.searchParams.has("newUX") || url.hash.includes("newUX");
29-
30-
return enableFluent ? (
33+
const useFluent = url.searchParams.has("newUX") || url.hash.includes("newUX");
34+
const contextValue = {
35+
useFluent,
36+
disableCopy: !!props.disableCopy,
37+
toolName: props.toolName,
38+
};
39+
return useFluent ? (
3140
<FluentProvider theme={props.customTheme || webDarkTheme}>
32-
<ToolContext.Provider value={{ useFluent: true, disableCopy: !!props.disableCopy }}>{props.children}</ToolContext.Provider>
41+
<ToolContext.Provider value={contextValue}>{props.children}</ToolContext.Provider>
3342
</FluentProvider>
3443
) : (
35-
props.children
44+
<ToolContext.Provider value={contextValue}>{props.children}</ToolContext.Provider>
3645
);
3746
};
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import { Body1Strong, makeStyles, tokens } from "@fluentui/react-components";
2+
import type { FluentIcon } from "@fluentui/react-icons";
3+
import type { FunctionComponent, PropsWithChildren } from "react";
4+
5+
const useStyles = makeStyles({
6+
rootDiv: {
7+
flex: 1,
8+
overflow: "hidden",
9+
display: "flex",
10+
flexDirection: "column",
11+
},
12+
icon: {
13+
width: tokens.fontSizeBase400,
14+
height: tokens.fontSizeBase400,
15+
verticalAlign: "middle",
16+
},
17+
header: {
18+
height: tokens.fontSizeBase400,
19+
fontSize: tokens.fontSizeBase400,
20+
textAlign: "center",
21+
verticalAlign: "middle",
22+
},
23+
});
24+
25+
export type PaneProps = {
26+
title: string;
27+
icon?: FluentIcon;
28+
};
29+
export const Pane: FunctionComponent<PropsWithChildren<PaneProps>> = (props) => {
30+
const classes = useStyles();
31+
return (
32+
<div className={classes.rootDiv}>
33+
<div className={classes.header}>
34+
{props.icon ? (
35+
<props.icon className={classes.icon} />
36+
) : (
37+
<img className={classes.icon} id="logo" src="https://www.babylonjs.com/Assets/logo-babylonjs-social-twitter.png" />
38+
)}
39+
<Body1Strong id="title">{props.title}</Body1Strong>
40+
</div>
41+
{props.children}
42+
</div>
43+
);
44+
};

packages/dev/sharedUiComponents/src/fluent/hoc/propertyLine.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { Body1, Body1Strong, Button, InfoLabel, Link, ToggleButton, makeStyles, tokens } from "@fluentui/react-components";
22
import { Collapse } from "@fluentui/react-motion-components-preview";
33
import { AddFilled, CopyRegular, SubtractFilled } from "@fluentui/react-icons";
4-
import type { FunctionComponent, PropsWithChildren } from "react";
4+
import type { FunctionComponent, HTMLProps, PropsWithChildren } from "react";
55
import { useContext, useState, forwardRef } from "react";
66
import { copyCommandToClipboard } from "../../copyCommandToClipboard";
77
import { ToolContext } from "./fluentToolWrapper";
@@ -81,10 +81,10 @@ export type PropertyLineProps = {
8181
docLink?: string;
8282
};
8383

84-
export const LineContainer = forwardRef<HTMLDivElement, PropsWithChildren>((props, ref) => {
84+
export const LineContainer = forwardRef<HTMLDivElement, PropsWithChildren<HTMLProps<HTMLDivElement>>>((props, ref) => {
8585
const classes = usePropertyLineStyles();
8686
return (
87-
<div ref={ref} className={classes.container}>
87+
<div ref={ref} className={classes.container} {...props}>
8888
{props.children}
8989
</div>
9090
);

packages/dev/sharedUiComponents/src/fluent/primitives/accordion.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ const useStyles = makeStyles({
1212
display: "flex",
1313
flexDirection: "column",
1414
rowGap: tokens.spacingVerticalM,
15+
height: "100%",
1516
},
1617
panelDiv: {
1718
display: "flex",
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import { Field, SearchBox as FluentSearchBox, makeStyles } from "@fluentui/react-components";
2+
import type { InputOnChangeData } from "@fluentui/react-components";
3+
import type { SearchBoxChangeEvent } from "@fluentui/react-components";
4+
5+
type SearchProps = {
6+
onChange: (val: string) => void;
7+
placeholder?: string;
8+
};
9+
const useStyles = makeStyles({
10+
search: {
11+
minWidth: "50px",
12+
},
13+
});
14+
export const SearchBox = (props: SearchProps) => {
15+
const classes = useStyles();
16+
const onChange: (ev: SearchBoxChangeEvent, data: InputOnChangeData) => void = (_, data) => {
17+
props.onChange(data.value);
18+
};
19+
20+
return (
21+
<Field>
22+
<FluentSearchBox className={classes.search} placeholder={props.placeholder} onChange={onChange} />
23+
</Field>
24+
);
25+
};

packages/dev/sharedUiComponents/src/lines/lineContainerComponent.tsx

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ import * as React from "react";
22
import { DataStorage } from "core/Misc/dataStorage";
33
import type { ISelectedLineContainer } from "./iSelectedLineContainer";
44
import downArrow from "./downArrow.svg";
5+
import { AccordionSection } from "../fluent/primitives/accordion";
6+
import { ToolContext } from "../fluent/hoc/fluentToolWrapper";
57

68
interface ILineContainerComponentProps {
79
selection?: ISelectedLineContainer;
@@ -69,7 +71,11 @@ export class LineContainerComponent extends React.Component<ILineContainerCompon
6971
}
7072
}
7173

72-
override render() {
74+
renderFluent() {
75+
return <AccordionSection title={this.props.title}>{this.props.children}</AccordionSection>;
76+
}
77+
78+
renderOriginal() {
7379
if (!this.state.isExpanded) {
7480
return (
7581
<div className="paneContainer">
@@ -88,4 +94,8 @@ export class LineContainerComponent extends React.Component<ILineContainerCompon
8894
</div>
8995
);
9096
}
97+
98+
override render() {
99+
return <ToolContext.Consumer>{({ useFluent }) => (useFluent ? this.renderFluent() : this.renderOriginal())}</ToolContext.Consumer>;
100+
}
91101
}

packages/tools/nodeEditor/src/components/nodeList/nodeListComponent.tsx

Lines changed: 37 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@ import deleteButton from "../../imgs/delete.svg";
1414
import { NodeLedger } from "shared-ui-components/nodeGraphSystem/nodeLedger";
1515

1616
import "./nodeList.scss";
17+
import { ToolContext } from "shared-ui-components/fluent/hoc/fluentToolWrapper";
18+
import { Accordion } from "shared-ui-components/fluent/primitives/accordion";
19+
import { SearchBox } from "shared-ui-components/fluent/primitives/searchBox";
1720

1821
interface INodeListComponentProps {
1922
globalState: GlobalState;
@@ -327,6 +330,38 @@ export class NodeListComponent extends React.Component<INodeListComponentProps,
327330
}
328331
}
329332

333+
renderFluent(blockMenu: JSX.Element[]) {
334+
return (
335+
<div>
336+
<SearchBox placeholder="Filter" onChange={(val) => this.filterContent(val.toString())} />
337+
<Accordion>{blockMenu}</Accordion>
338+
</div>
339+
);
340+
}
341+
342+
renderOriginal(blockMenu: JSX.Element[]) {
343+
return (
344+
<div id="nmeNodeList">
345+
<div className="panes">
346+
<div className="pane">
347+
<div className="filter">
348+
<input
349+
type="text"
350+
placeholder="Filter"
351+
onFocus={() => (this.props.globalState.lockObject.lock = true)}
352+
onBlur={() => {
353+
this.props.globalState.lockObject.lock = false;
354+
}}
355+
onChange={(evt) => this.filterContent(evt.target.value)}
356+
/>
357+
</div>
358+
<div className="list-container">{blockMenu}</div>
359+
</div>
360+
</div>
361+
</div>
362+
);
363+
}
364+
330365
override render() {
331366
const customFrameNames: string[] = [];
332367
for (const frame in this._customFrameList) {
@@ -556,7 +591,7 @@ export class NodeListComponent extends React.Component<INodeListComponentProps,
556591
}
557592

558593
// Create node menu
559-
const blockMenu = [];
594+
const blockMenu: JSX.Element[] = [];
560595
for (const key in allBlocks) {
561596
const blockList = allBlocks[key]
562597
.filter((b: string) => !this.state.filter || b.toLowerCase().indexOf(this.state.filter.toLowerCase()) !== -1)
@@ -661,25 +696,6 @@ export class NodeListComponent extends React.Component<INodeListComponentProps,
661696
};
662697
}
663698

664-
return (
665-
<div id="nmeNodeList">
666-
<div className="panes">
667-
<div className="pane">
668-
<div className="filter">
669-
<input
670-
type="text"
671-
placeholder="Filter"
672-
onFocus={() => (this.props.globalState.lockObject.lock = true)}
673-
onBlur={() => {
674-
this.props.globalState.lockObject.lock = false;
675-
}}
676-
onChange={(evt) => this.filterContent(evt.target.value)}
677-
/>
678-
</div>
679-
<div className="list-container">{blockMenu}</div>
680-
</div>
681-
</div>
682-
</div>
683-
);
699+
return <ToolContext.Consumer>{({ useFluent }) => (useFluent ? this.renderFluent(blockMenu) : this.renderOriginal(blockMenu))}</ToolContext.Consumer>;
684700
}
685701
}

0 commit comments

Comments
 (0)