-
Notifications
You must be signed in to change notification settings - Fork 31
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: Core plugins refactor, XComponent framework (#2150)
- Add XComponent (eXtendable Component) framework to allow components to be wrapped/replaced - Similar to Swizzling in Docusaurus - Useful for components that we need to replace at the Enterprise level (e.g. `WidgetPanelTooltip` needs to display the query name in Enterprise) - Added to the StyleGuide - Pass a `VariableDescriptor` to `WidgetPanel` and `WidgetPanelTooltip` - Allows for more information to be included, e.g. a `QueryVariableDescriptor` which extends `VariableDescriptor` - Refactor the Core plugins to be consistent in how they handle the `PanelEvent.OPEN` event - Will allow Enterprise to use these plugins straight up and remove their duplicated panel code - Add functions for `PanelEvent.OPEN` - Instead of using `PanelEvent.OPEN` directly and not getting any type safety, add functions for emitting, listening, and a hook to enforce type safety
- Loading branch information
Showing
39 changed files
with
1,073 additions
and
333 deletions.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,123 @@ | ||
import React, { useState } from 'react'; | ||
import { | ||
XComponentMapProvider, | ||
createXComponent, | ||
Button, | ||
} from '@deephaven/components'; | ||
import SampleSection from './SampleSection'; | ||
|
||
type FooComponentProps = { value: string }; | ||
|
||
function FooComponent({ value }: FooComponentProps) { | ||
return ( | ||
<Button kind="primary" onClick={() => undefined}> | ||
{value} | ||
</Button> | ||
); | ||
} | ||
FooComponent.displayName = 'FooComponent'; | ||
|
||
// Create an XComponent from FooComponent to allow for replacement | ||
const XFooComponent = createXComponent(FooComponent); | ||
|
||
function NestedFooComponent({ value }: FooComponentProps) { | ||
// We're using the XComponent version so this panel can be replaced if it is mapped from a parent context to a replacement | ||
return <XFooComponent value={`${value}.${value}`} />; | ||
} | ||
|
||
function MultiFooComponent({ value }: FooComponentProps) { | ||
// Show multiple instances getting replaced | ||
return ( | ||
<div> | ||
<XFooComponent value={value} /> | ||
<XFooComponent value={value} /> | ||
</div> | ||
); | ||
} | ||
|
||
// What we're replacing the XFooComponent with. | ||
function ReverseFooComponent({ value }: FooComponentProps) { | ||
return ( | ||
<Button kind="danger" onClick={() => undefined}> | ||
{value.split('').reverse().join('')} | ||
</Button> | ||
); | ||
} | ||
|
||
/** | ||
* Some examples showing usage of XComponents. | ||
*/ | ||
export function XComponents(): JSX.Element { | ||
const [value, setValue] = useState('hello'); | ||
|
||
return ( | ||
<SampleSection name="xcomponents"> | ||
<h2 className="ui-title">XComponents</h2> | ||
<p> | ||
XComponents are a way to replace a component with another component | ||
without needing to pass props all the way down the component tree. This | ||
can be useful in cases where we have a component deep down in the | ||
component tree that we want to replace with a different component, but | ||
don't want to have to provide props at the top level just to hook | ||
into that. | ||
<br /> | ||
Below is a component that is simply a button displaying the text | ||
inputted in the input field. We will replace this component with a new | ||
component that reverses the text, straight up, then in a nested | ||
scenario, and then multiple instances. | ||
</p> | ||
<div className="form-group"> | ||
<label htmlFor="xcomponentsInput"> | ||
Input Value: | ||
<input | ||
type="text" | ||
className="form-control" | ||
id="xcomponentsInput" | ||
value={value} | ||
onChange={e => setValue(e.target.value)} | ||
/> | ||
</label> | ||
</div> | ||
<div className="row"> | ||
<div className="col"> | ||
<small>Original Component</small> | ||
<div> | ||
<XFooComponent value={value} /> | ||
</div> | ||
|
||
<small>Replaced with Reverse</small> | ||
<div> | ||
<XComponentMapProvider | ||
value={new Map([[XFooComponent, ReverseFooComponent]])} | ||
> | ||
<XFooComponent value={value} /> | ||
</XComponentMapProvider> | ||
</div> | ||
</div> | ||
<div className="col"> | ||
<small>Nested component replaced</small> | ||
<div> | ||
<XComponentMapProvider | ||
value={new Map([[XFooComponent, ReverseFooComponent]])} | ||
> | ||
{/* The `FooComponent` that gets replaced is from within the `NestedFooComponent` */} | ||
<NestedFooComponent value={value} /> | ||
</XComponentMapProvider> | ||
</div> | ||
</div> | ||
<div className="col"> | ||
<small>Multiple Components replaced</small> | ||
<div> | ||
<XComponentMapProvider | ||
value={new Map([[XFooComponent, ReverseFooComponent]])} | ||
> | ||
<MultiFooComponent value={value} /> | ||
</XComponentMapProvider> | ||
</div> | ||
</div> | ||
</div> | ||
</SampleSection> | ||
); | ||
} | ||
|
||
export default XComponents; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
import React, { PropsWithChildren } from 'react'; | ||
// We only use react-redux from tests in @deephaven/components, so it is only added as a devDependency | ||
import { connect } from 'react-redux'; | ||
import { | ||
canHaveRef, | ||
isClassComponent, | ||
isWrappedComponent, | ||
isForwardRefComponentType, | ||
} from './ComponentUtils'; | ||
|
||
function TestComponent() { | ||
return <div>Test</div>; | ||
} | ||
|
||
class TestClass extends React.PureComponent<PropsWithChildren<never>> { | ||
render() { | ||
return <div>Test</div>; | ||
} | ||
} | ||
|
||
test('isForwardRefComponent', () => { | ||
expect(isForwardRefComponentType(TestComponent)).toBe(false); | ||
expect(isForwardRefComponentType(React.forwardRef(TestComponent))).toBe(true); | ||
expect(isForwardRefComponentType(TestClass)).toBe(false); | ||
expect(isForwardRefComponentType(connect(null, null)(TestComponent))).toBe( | ||
false | ||
); | ||
expect(isForwardRefComponentType(connect(null, null)(TestClass))).toBe(false); | ||
}); | ||
|
||
test('isClassComponent', () => { | ||
expect(isClassComponent(TestComponent)).toBe(false); | ||
expect(isClassComponent(TestClass)).toBe(true); | ||
expect(isClassComponent(React.forwardRef(TestComponent))).toBe(false); | ||
expect(isClassComponent(connect(null, null)(TestComponent))).toBe(false); | ||
expect(isClassComponent(connect(null, null)(TestClass))).toBe(true); | ||
}); | ||
|
||
test('isWrappedComponent', () => { | ||
expect(isWrappedComponent(TestComponent)).toBe(false); | ||
expect(isWrappedComponent(TestClass)).toBe(false); | ||
expect(isWrappedComponent(connect(null, null)(TestComponent))).toBe(true); | ||
expect(isWrappedComponent(React.forwardRef(TestComponent))).toBe(false); | ||
expect(isWrappedComponent(connect(null, null)(TestClass))).toBe(true); | ||
}); | ||
|
||
test('canHaveRef', () => { | ||
const forwardedType = React.forwardRef(TestComponent); | ||
|
||
expect(canHaveRef(TestComponent)).toBe(false); | ||
expect(canHaveRef(forwardedType)).toBe(true); | ||
expect(canHaveRef(TestClass)).toBe(true); | ||
expect(canHaveRef(connect(null, null)(TestClass))).toBe(true); | ||
expect( | ||
canHaveRef(connect(null, null, null, { forwardRef: true })(TestClass)) | ||
).toBe(true); | ||
}); |
Oops, something went wrong.