Skip to content

Commit

Permalink
Merge pull request #2151 from FormidableLabs/allow-user-props-foundation
Browse files Browse the repository at this point in the history
Added functionality to be able to pass user props to components. Safe…
  • Loading branch information
Becca Bailey authored Mar 29, 2022
2 parents 7c5bfd6 + c5ee839 commit 81b21f2
Show file tree
Hide file tree
Showing 13 changed files with 204 additions and 54 deletions.
2 changes: 2 additions & 0 deletions demo/ts/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import PolarAxisDemo from "./components/victory-polar-axis-demo";
import PrimitivesDemo from "./components/primitives-demo";
import ScatterDemo from "./components/victory-scatter-demo";
import SelectionDemo from "./components/selection-demo";
import StackDemo from "./components/victory-stack-demo";
import TooltipDemo from "./components/victory-tooltip-demo";
import VictoryDemo from "./components/victory-demo";
import VictorySelectionContainerDemo from "./components/victory-selection-container-demo";
Expand Down Expand Up @@ -68,6 +69,7 @@ const MAP = {
"/primitives": { component: PrimitivesDemo, name: "PrimitivesDemo" },
"/scatter": { component: ScatterDemo, name: "ScatterDemo" },
"/selection": { component: SelectionDemo, name: "SelectionDemo" },
"/stack": { component: StackDemo, name: "StackDemo" },
"/tooltip": { component: TooltipDemo, name: "TooltipDemo" },
"/victory-demo": { component: VictoryDemo, name: "VictoryDemo" },
"/victory-selection-container": {
Expand Down
14 changes: 14 additions & 0 deletions demo/ts/components/group-demo.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,8 @@ class App extends React.Component {
colorScale="qualitative"
>
<VictoryGroup
aria-label="Purple Victory Grouping"
data-testing="Testing this shows up"
color={"purple"}
data={[
{ x: 1, y: 3 },
Expand Down Expand Up @@ -160,6 +162,18 @@ class App extends React.Component {
/>
</VictoryGroup>
</VictoryChart>

<VictoryGroup
aria-label="Stand Alone Group"
data-testing="Testing this shows up"
y={(data) => Math.tan(2 * Math.PI * data.x)}
>
<VictoryLine />
<VictoryVoronoi
labelComponent={<VictoryTooltip />}
labels={({ datum }) => datum.y}
/>
</VictoryGroup>
</div>
</div>
);
Expand Down
20 changes: 17 additions & 3 deletions demo/ts/components/victory-chart-demo.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -236,11 +236,21 @@ class VictoryChartDemo extends React.Component<any, VictoryChartDemoState> {
<div className="demo">
<h1>VictoryChart</h1>
<div style={containerStyle}>
<VictoryChart style={chartStyle} polar>
<VictoryChart
style={chartStyle}
polar
title="Victory Polar Scatter Chart"
aria-label="Victory Polar Scatter Chart"
data-some-user-prop="TESTING 123"
desc="Circular graph with a twirl pattern of data points."
>
<VictoryScatter />
</VictoryChart>

<VictoryChart style={assign({}, chartStyle, bgStyle)}>
<VictoryChart
style={assign({}, chartStyle, bgStyle)}
title="Victory Scatter Chart"
>
<VictoryScatter
data={[
{ x: -3, y: -3 },
Expand All @@ -254,7 +264,11 @@ class VictoryChartDemo extends React.Component<any, VictoryChartDemoState> {
<VictoryScatter />
</VictoryChart>

<VictoryChart style={chartStyle} domainPadding={{ x: [0, 20] }}>
<VictoryChart
style={chartStyle}
domainPadding={{ x: [0, 20] }}
title="Victory Bar Chart"
>
<VictoryAxis dependentAxis style={axisStyle} />
<VictoryAxis style={axisStyle} tickCount={6} />
<VictoryBar
Expand Down
30 changes: 30 additions & 0 deletions demo/ts/components/victory-stack-demo.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import React from "react";
import { VictoryStack } from "@packages/victory-stack/src/index";
import { VictoryArea } from "@packages/victory-area/src/index";

class App extends React.Component {

render() {
return (
<div>
<h3 style={{textAlign: 'center'}}>Standalone Stack</h3>
<VictoryStack
aria-label="Victory Stack Demo"
data-some-user-prop="TESTING 123"
>
<VictoryArea
data={[{x: "a", y: 2}, {x: "b", y: 3}, {x: "c", y: 5}]}
/>
<VictoryArea
data={[{x: "a", y: 1}, {x: "b", y: 4}, {x: "c", y: 5}]}
/>
<VictoryArea
data={[{x: "a", y: 3}, {x: "b", y: 2}, {x: "c", y: 6}]}
/>
</VictoryStack>
</div>
)
}
}

export default App;
2 changes: 2 additions & 0 deletions packages/victory-chart/src/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export interface VictoryChartProps extends VictoryCommonProps {
backgroundComponent?: React.ReactElement;
categories?: CategoryPropType;
children?: React.ReactNode | React.ReactNode[];
desc?: string;
defaultAxes?: AxesType;
defaultPolarAxes?: AxesType;
domain?: DomainPropType;
Expand All @@ -33,6 +34,7 @@ export interface VictoryChartProps extends VictoryCommonProps {
style?: Pick<VictoryStyleInterface, "parent"> & {
background?: VictoryStyleObject;
};
title?: string;
}

export class VictoryChart extends React.Component<VictoryChartProps, any> {}
12 changes: 7 additions & 5 deletions packages/victory-chart/src/victory-chart.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,14 @@ import PropTypes from "prop-types";
import React from "react";
import {
Background,
CommonProps,
Helpers,
Hooks,
PropTypes as CustomPropTypes,
UserProps,
VictoryContainer,
VictoryTheme,
CommonProps,
PropTypes as CustomPropTypes,
Wrapper,
Hooks
} from "victory-core";
import { VictorySharedEvents } from "victory-shared-events";
import { VictoryAxis } from "victory-axis";
Expand Down Expand Up @@ -124,12 +125,13 @@ const VictoryChart = (initialProps) => {
const defaultContainerProps = defaults(
{},
containerComponent.props,
containerProps
containerProps,
UserProps.getSafeUserProps(initialProps)
);
return React.cloneElement(containerComponent, defaultContainerProps);
}
return groupComponent;
}, [groupComponent, standalone, containerComponent, containerProps]);
}, [groupComponent, standalone, containerComponent, containerProps, initialProps]);

const events = React.useMemo(() => {
return Wrapper.getAllEvents(props);
Expand Down
1 change: 1 addition & 0 deletions packages/victory-core/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ export * as Style from "./victory-util/style";
export * as TextSize from "./victory-util/textsize";
export { default as Timer } from "./victory-util/timer";
export * as Transitions from "./victory-util/transitions";
export * as UserProps from "./victory-util/user-props";
export * as CommonProps from "./victory-util/common-props";
export * as Wrapper from "./victory-util/wrapper";
export * as Axis from "./victory-util/axis";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import Portal from "../victory-portal/portal";
import PortalContext from "../victory-portal/portal-context";
import TimerContext from "../victory-util/timer-context";
import * as Helpers from "../victory-util/helpers";
import * as UserProps from "../victory-util/user-props";

export default class VictoryContainer extends React.Component {
static displayName = "VictoryContainer";
Expand Down Expand Up @@ -192,9 +193,13 @@ export default class VictoryContainer extends React.Component {
preserveAspectRatio,
role
} = this.props;

const style = responsive
? this.props.style
: Helpers.omit(this.props.style, ["height", "width"]);

const userProps = UserProps.getSafeUserProps(this.props);

const svgProps = assign(
{
width,
Expand All @@ -213,7 +218,8 @@ export default class VictoryContainer extends React.Component {
.filter(Boolean)
.join(" ") || undefined,
viewBox: responsive ? `0 0 ${width} ${height}` : undefined,
preserveAspectRatio: responsive ? preserveAspectRatio : undefined
preserveAspectRatio: responsive ? preserveAspectRatio : undefined,
...userProps
},
events
);
Expand Down
30 changes: 18 additions & 12 deletions packages/victory-core/src/victory-util/add-events.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
import * as Events from "./events";
import isEqual from "react-fast-compare";
import VictoryTransition from "../victory-transition/victory-transition";
import * as UserProps from '../victory-util/user-props';

const datumHasXandY = (datum) => {
return !isNil(datum._x) && !isNil(datum._y);
Expand Down Expand Up @@ -274,11 +275,14 @@ export default (WrappedComponent, options) => {
const parentProps = isContainer
? this.getComponentProps(component, "parent", "parent")
: {};

if (parentProps.events) {
this.globalEvents = Events.getGlobalEvents(parentProps.events);
parentProps.events = Events.omitGlobalEvents(parentProps.events);
}
return React.cloneElement(component, parentProps, children);

const componentProps = { ...parentProps, ...parentProps.userProps };
return React.cloneElement(component, componentProps, children);
}

animateComponent(props, defaultAnimationWhitelist) {
Expand Down Expand Up @@ -327,23 +331,24 @@ export default (WrappedComponent, options) => {

renderData(props, shouldRenderDatum = datumHasXandY) {
const { dataComponent, labelComponent, groupComponent } = props;

const userProps = UserProps.getSafeUserProps(props);

const dataComponents = this.dataKeys.reduce(
(validDataComponents, _dataKey, index) => {
const dataProps = this.getComponentProps(
dataComponent,
"data",
index
);
if (shouldRenderDatum(dataProps.datum)) {
validDataComponents.push(
React.cloneElement(dataComponent, dataProps)
);
}
return validDataComponents;
},
[]
);
if (shouldRenderDatum(dataProps.datum)) {
validDataComponents.push(
React.cloneElement(dataComponent, dataProps)
);
}
return validDataComponents;
},
[]
);

const labelComponents = this.dataKeys
.map((_dataKey, index) => {
Expand All @@ -360,7 +365,8 @@ export default (WrappedComponent, options) => {
.filter(Boolean);

const children = [...dataComponents, ...labelComponents];
return this.renderContainer(groupComponent, children);
const group = React.cloneElement(groupComponent, { ...userProps }, children);
return this.renderContainer(group, children);
}
};
};
64 changes: 64 additions & 0 deletions packages/victory-core/src/victory-util/user-props.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
/*
USER_PROPS_SAFELIST is to contain any string deemed safe for user props.
The startsWidth array will contain the start of any accepted user-prop that
starts with these characters.
The exactMatch will contain a list of exact prop names that are accepted.
*/
const USER_PROPS_SAFELIST = {
startsWith: ["data-", "aria-"],
exactMatch: []
};

/**
* doesPropStartWith: Function that takes a prop's key and runs it against all
* options in the USER_PROPS_SAFELIST and checks to see if it starts with any
* of those options.
* @param {string} key: prop key to be tested against whitelist
* @returns {Boolean}: returns true if the key starts with an option or false if
* otherwise
*/
const doesPropStartWith = (key) => {
let startsWith = false;

USER_PROPS_SAFELIST.startsWith.forEach((starterString) => {
const regex = new RegExp(`\\b(${starterString})(\\w|-)+`, "g");
if (regex.test(key)) startsWith = true;
});

return startsWith;
};

/**
* isExactMatch: checks to see if the given key matches any of the 'exactMatch'
* items in the whitelist
* @param {String} key: prop key to be tested against the whitelist-exact match
* array.
* @returns {Boolean}: return true if whitelist contains that key, otherwise
* returns false.
*/
const isExactMatch = (key) => USER_PROPS_SAFELIST.exactMatch.includes(key);

/**
* testIfSafeProp: tests prop's key against both startsWith and exactMatch values
* @param {String} key: prop key to be tested against the whitelist
* @returns {Boolean}: returns true if found in whitelist, otherwise returns false
*/
const testIfSafeProp = (key) => {
if (doesPropStartWith(key) || isExactMatch(key)) return true;
return false;
};

/**
* getSafeUserProps - function that takes in a props object and removes any
* key-value entries that do not match filter strings in the USER_PROPS_SAFELIST
* object.
*
* @param {Object} props: props to be filtered against USER_PROPS_SAFELIST
* @returns {Object}: object containing remaining acceptable props
*/
export const getSafeUserProps = (props) => {
const propsToFilter = { ...props };
return Object.fromEntries(
Object.entries(propsToFilter).filter(([key]) => testIfSafeProp(key))
);
};
15 changes: 10 additions & 5 deletions packages/victory-group/src/victory-group.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,13 @@ import { assign, defaults, isEmpty } from "lodash";
import PropTypes from "prop-types";
import React from "react";
import {
CommonProps,
Helpers,
Hooks,
UserProps,
VictoryContainer,
VictoryTheme,
CommonProps,
Wrapper,
Hooks
} from "victory-core";
import { VictorySharedEvents } from "victory-shared-events";
import { getChildren, useMemoizedProps } from "./helper-methods";
Expand Down Expand Up @@ -88,17 +89,21 @@ const VictoryGroup = (initialProps) => {
name
]);

const userProps = React.useMemo(() => UserProps.getSafeUserProps(initialProps), [initialProps]);

const container = React.useMemo(() => {
if (standalone) {
const defaultContainerProps = defaults(
{},
containerComponent.props,
containerProps
containerProps,
userProps
);
return React.cloneElement(containerComponent, defaultContainerProps);
}
return groupComponent;
}, [groupComponent, standalone, containerComponent, containerProps]);

return React.cloneElement(groupComponent, userProps);
}, [groupComponent, standalone, containerComponent, containerProps, userProps]);

const events = React.useMemo(() => {
return Wrapper.getAllEvents(props);
Expand Down
Loading

0 comments on commit 81b21f2

Please sign in to comment.