react-spring clean scale and translateX transition - javascript

The react-swipeable-views library is providing example usages. I want to reuse the coverflow example, but in a react functional comp. way. I almost managed to get it working. However on my implementation, the swipeable element can get stuck during swipe, if you swipe slowly (the scale is not applied anymore). See screenshot:
In the demo of react-swipeable views this is somehow not happening. This example is using react-spring for the animation transitions. I provided a stackblitz demo that is reproducable and maybe you can find out the issue.
component
const useStyles = makeStyles((theme) => ({
root: {
background: theme.palette.background.paper,
padding: theme.spacing(0, 6),
},
img: {
width: 180,
height: 180,
display: "block",
marginBottom: theme.spacing(2),
},
container: {
padding: theme.spacing(2),
borderRadius: 4,
justifyContent: "center",
maxWidth: 320,
margin: "auto",
},
slide: {
padding: theme.spacing(3, 2),
color: theme.palette.text.primary,
alignItems: "center",
justifyContent: "center",
flexDirection: "column",
display: "flex",
},
}));
const albums = [
{
name: "Abbey Road",
src: "https://picsum.photos/200/300",
},
{
name: "Bat Out of Hell",
src: "https://picsum.photos/200/300",
},
{
name: "Homogenic",
src: "https://picsum.photos/200/300",
},
{
name: "Number of the Beast",
src: "https://picsum.photos/200/300",
},
{
name: "It's Blitz",
src: "https://picsum.photos/200/300",
},
{
name: "The Man-Machine",
src: "https://picsum.photos/200/300",
},
];
export function StatisticSelector() {
const classes = useStyles();
const [index, setIndex] = useState(0);
const [props, start] = useSpring(() => ({
from: { position: 0 },
}));
function handleChangeIndex(indexNum) {
setIndex(indexNum);
}
function handleSwitch(index, type) {
if (type === "end") {
start({
from: { position: props.position.value },
to: { position: Math.round(index) },
});
return;
}
props.position.setValue(index);
}
function interpolatePositionProps(range, output) {
return props.position.interpolate({
range,
output,
});
}
return (
<div className={classes.container}>
<SwipeableViews
index={index}
className={classes.root}
onChangeIndex={handleChangeIndex}
onSwitching={handleSwitch}
enableMouseEvents
>
{albums.map((album, currentIndex) => {
const inputRange = albums.map((_, i) => i);
const scale = interpolatePositionProps(
inputRange,
inputRange.map((i) => (currentIndex === i ? 1 : 0.7))
).interpolate((x) => `scale(${x})`);
const opacity = interpolatePositionProps(
inputRange,
inputRange.map((i) => (currentIndex === i ? 1 : 0.3))
);
const translateX = interpolatePositionProps(
inputRange,
inputRange.map((i) => (100 / 2) * (i - currentIndex))
).interpolate((x) => `translateX(${x}px)`);
const scaleAndTranslateX = interpolate(
[scale, translateX],
(scale, translateX) => `${scale} ${translateX}`
);
return (
<animated.div
key={String(currentIndex)}
className={classes.slide}
style={Object.assign({
opacity,
transform: scaleAndTranslateX,
})}
>
<img className={classes.img} src={album.src} alt="cover" />
<Button variant="contained" color="primary" size="small">
Select
</Button>
</animated.div>
);
})}
</SwipeableViews>
</div>
);
}

function handleSwitch(index, type) {
if (type === "end") {
start({
from: { position: props.position.value },
to: { position: Math.round(index) }
});
/**
* Solution:
* Do not stop executing this function and make final value update.
* Just comment `return` below, and everything will work as expected.
*/
// return;
}
props.position.setValue(index);
}

Related

Update name of selected node from sidebar in React Flow

I am very new to react.js and JavaScript, so apologies in advance.
I am trying to make a very concise diagramming tool in my webpage, where the user can create their own use case diagram.
I am looking at these examples:
https://reactflow.dev/examples/drag-and-drop/
https://reactflow.dev/examples/update-node/
I got the drag and drop to work, but am having a hard time applying the update node feature. My final goal is to let the user select a node, and update its name via input at the sidebar.
I started with adding const onElementClick = (event, element) => event.dataTransfer.getData("label"); to the main file to get the label from the sidebar, and am trying to send back the changed label by adding the block code below.
<label> label: </label>
<input value={nodeName} onChange={(evt) => setNodeName(evt.target.value)} />
Because their example on https://reactflow.dev/examples/update-node/ has everything in a single file, and am not able to update the node's name with selection, I am just a bit unsure how to proceed from here.
Any help would be sincerely appreciated, as a beginner in react.
Again, apologies and thanks in advance!
main.js
import Sidebar from "./Sidebar";
const initialElements = [
{
id: "1",
type: "input",
data: { label: "User" },
position: { x: 300, y: 150 },
},
{
id: "2",
type: "default",
data: { label: "Use Case" },
position: { x: 700, y: 250 },
},
{
id: "3",
type: "output",
data: { label: "Database" },
position: { x: 1100, y: 350 },
},
];
let id = 0;
const getId = () => `dndnode_${id++}`;
export const Tester = () => {
const reactFlowWrapper = useRef(null);
const [reactFlowInstance, setReactFlowInstance] = useState(null);
const [elements, setElements] = useState(initialElements);
const [nodeName, setNodeName] = useState("Node 1");
const [nodeBg, setNodeBg] = useState("#eee");
const [nodeHidden, setNodeHidden] = useState(false);
const onElementClick = (event, element) => event.dataTransfer.getData("click", element);
const onConnect = (params) => setElements((els) => addEdge(params, els));
const onElementsRemove = (elementsToRemove) =>
setElements((els) => removeElements(elementsToRemove, els));
const onLoad = (_reactFlowInstance) => setReactFlowInstance(_reactFlowInstance);
const onDragOver = (event) => {
event.preventDefault();
event.dataTransfer.dropEffect = "move";
};
const onDrop = (event) => {
event.preventDefault();
const reactFlowBounds = reactFlowWrapper.current.getBoundingClientRect();
const type = event.dataTransfer.getData("application/reactflow");
const label = event.dataTransfer.getData("label");
const position = reactFlowInstance.project({
x: event.clientX - reactFlowBounds.left,
y: event.clientY - reactFlowBounds.top,
});
const newNode = {
id: getId(),
type,
position,
data: { label: label },
};
setElements((es) => es.concat(newNode));
};
return (
<div className="dndflow">
<ReactFlowProvider>
<Box
className="reactflow-wrapper"
ref={reactFlowWrapper}
style={{ height: 600 }}
sx={{ display: "flex", justifyContent: "space-between", justifyContent: "center" }}
>
<ReactFlow
elements={elements}
onConnect={onConnect}
onElementsRemove={onElementsRemove}
onLoad={onLoad}
onDrop={onDrop}
onDragOver={onDragOver}
onElementClick={onElementClick}
>
<Controls />
<MiniMap />
<Background variant="lines" size={1.5} gap={20} color="#eee" />
</ReactFlow>
<Box sx={{ ml: 10 }}>
<Sidebar />
</Box>
</Box>
</ReactFlowProvider>
</div>
);
};
Sidebar.js
export default () => {
const onDragStart = (event, nodeType, label) => {
event.dataTransfer.setData("application/reactflow", nodeType);
event.dataTransfer.setData("label", label);
event.dataTransfer.effectAllowed = "move";
};
const changeNodeName = (event, nodetype, label) => {
//event.dataTransfer.getData()
const [nodeName, setNodeName] = useState("Node 1");
};
return (
<aside>
<Box
sx={{
display: "flex",
flexDirection: "column",
alignItems: "center",
justifyContent: "center",
}}
>
<div className="description">Drag nodes to the pane.</div>
<Box
className="dndnode input"
onDragStart={(event) => onDragStart(event, "input", "User")}
draggable
sx={{
mt: 2,
border: 1,
borderColor: "blue",
borderRadius: 0.5,
width: 200,
height: 50,
textAlign: "center",
py: 1.4,
"&:hover": {
opacity: 0.5,
},
}}
>
User
</Box>
<Box
className="dndnode"
onDragStart={(event) => onDragStart(event, "default", "Use Case")}
draggable
sx={{
mt: 2,
border: 0.5,
borderColor: "black",
borderRadius: 0.5,
width: 200,
height: 50,
textAlign: "center",
py: 1.4,
"&:hover": {
opacity: 0.5,
},
}}
>
Use Case
</Box>
<Box
className="dndnode output"
onDragStart={(event) => onDragStart(event, "output", "Database")}
draggable
sx={{
mt: 2,
border: 0.5,
borderColor: "red",
borderRadius: 0.5,
width: 200,
height: 50,
textAlign: "center",
py: 1.4,
"&:hover": {
opacity: 0.5,
},
}}
>
Database
</Box>
</Box>
</aside>
);
};
I get the same problem as you, but after a while, I solve this problem after reading it many times in the docs https://reactflow.dev/docs/api/react-flow-props/,
you can use onNodeClick(event: React.MouseEvent, node: Node) and set a state like show-sidebar that will change every time u click on the nodes set-Show-Sidebar( !show-sidebar), so when it gets true u send the node-id to Sidebar change it and receiver it, change it with onChange function then update nodes with
nodes.map((node)=>{node.id === id ? {...node, label:newName } : node})

scrollToIndex out of range: request index1 but maximum is -1 .. React Native

when I run my application it's okay and work If I create an array and put it in the data in FlatList like this array
const photos = [
{ id: 1, title: "Photo 1" },
{ id: 2, title: "Photo 2" },
{ id: 3, title: "Photo 3" },
{ id: 4, title: "Photo 4" },
{ id: 5, title: "Photo 5" },
{ id: 6, title: "Photo 6" },
];
But when I replace the photos array with an API, The app doesn't work. I tried more than API, I think the error is in my code not in the API,
This error appears to me " scrollToIndex out of range: request index1 but maximum is -1 "
What's wrong with my code?
import React, { useState, useRef, useEffect } from "react";
import {
StyleSheet,
View,
FlatList,
Dimensions,
Text,
TouchableOpacity,
} from "react-native";
import { AntDesign } from "#expo/vector-icons";
import axios from "axios";
const phoneWidth = Dimensions.get("screen").width;
const phoneHeight = Dimensions.get("screen").height;
function ScrollScreen() {
const [index, setIndex] = useState(0);
const [border, setBorder] = useState(0);
const refContainer = useRef();
const refBox = useRef();
const [data, setData] = useState([]);
useEffect(() => {
photos();
}, []);
function photos() {
axios
.get("https://jsonplaceholder.typicode.com/photos")
.then(async function (response) {
setData(response.data);
})
.catch((err) => console.error(err));
}
useEffect(() => {
refContainer.current.scrollToIndex({ animated: true, index });
}, [index]);
useEffect(() => {
refBox.current.scrollToIndex({ animated: true, index });
}, [index]);
const theNext = () => {
if (index < photos.length - 1) {
setIndex(index + 1);
setBorder(index + 1);
}
};
const thePrevious = () => {
if (index > 0) {
setIndex(index - 1);
setBorder(index - 1);
}
};
return (
<View style={styles.con}>
<AntDesign
style={[styles.iconConPosition, { left: phoneWidth * 0.05 }]}
onPress={thePrevious}
size={55}
color="#0dddcb"
name="caretleft"
/>
<AntDesign
style={[styles.iconConPosition, { right: phoneWidth * 0.05 }]}
onPress={theNext}
size={55}
color="#0dddcb"
name="caretright"
/>
<FlatList
scrollEnabled={false}
ref={refContainer}
data={data}
// data={photos}
keyExtractor={(item, index) => item.id.toString()}
style={styles.flatList}
renderItem={({ item, index }) => (
<View
style={{
height: 150,
width: phoneWidth * 0.7,
margin: 50,
backgroundColor: "red",
alignSelf: "center",
justifyContent: "center",
alignItems: "center",
}}
>
<Text>{item.id}</Text>
<Text>{item.title}</Text>
</View>
)}
horizontal
pagingEnabled //تفعيل خاصية التمرير
showsHorizontalScrollIndicator={false}
/>
<FlatList
ref={refBox}
data={data}
// data={photos}
keyExtractor={(item, index) => item.id.toString()}
style={styles.flatList}
renderItem={({ item, index }) => (
<TouchableOpacity
onPress={() => {
setIndex(index);
setBorder(index);
}}
style={
border === index
? {
height: 100,
width: phoneWidth * 0.4,
margin: 7,
backgroundColor: "gray",
alignSelf: "center",
justifyContent: "center",
alignItems: "center",
borderWidth: 2,
borderColor: "blue",
}
: {
height: 100,
width: phoneWidth * 0.4,
margin: 7,
backgroundColor: "gray",
alignSelf: "center",
justifyContent: "center",
alignItems: "center",
}
}
>
<Text>{item.id}</Text>
<Text>{item.title}</Text>
</TouchableOpacity>
)}
horizontal
/>
<Text>{index}</Text>
</View>
);
}
export default ScrollScreen;
Initially data is an empty array, but index is set to 0. On the first invocation of the useEffect that tries to scroll, there is an error because scrollToIndex(0) is an error when data is empty, since there is no item for index 0.
Try initializing the border and index state to -1 instead of 0 like:
const [index, setIndex] = useState(-1);
const [border, setBorder] = useState(-1);
On a separate but related note the theNext function has an error, it should be checking data.length instead of photos.length.
Ran into the same issue just now. Using ScrollToOffSet({offset: number, animated: boolean}) instead of scrollToIndex solved the issue for me

React Native - Changing the indicator width according to tab bar text width (react-native-tab-view)

I am using react-native-tab-view, and trying to change the indicator width. I would like indicator width to be the same with the tab text. But What I did is just the default. I have tried in many ways, but always it gave me the wrong result. The tab bar should be scrollable horizontally as well. Could you check which part I should change?
This is the expected result :
ShowAllIndex Code :
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: colors.dark,
},
});
const ShowAllIndex = () => {
const { seller } = useSelector((s) => s.auth, shallowEqual);
const [routes] = useState([
{ key: 'best', title: 'BEST' },
{ key: 'jacket', title: '아우터' },
{ key: 'pants', title: '바지' },
{ key: 'skirts', title: '스커트' },
{ key: 'topClothe', title: '원피스' },
{ key: 'one', title: '바지' },
{ key: 'two', title: '스커트' },
{ key: 'three', title: '상의' },
]);
const renderScene = SceneMap({
best: ShowAllMainRoutes,
jacket: JacketRoutes,
pants: PantsRoutes,
skirts: SkirtsRoutes,
topClothe: TopClotheRoutes,
one: ShowAllMainRoutes,
two: JacketRoutes,
three: PantsRoutes,
});
return (
<ScrollView style={[styles.container, { marginTop: Global() }]}>
<CustomTabView
routes={routes}
renderScene={renderScene}
scrollEnabled={true}
tabStyle={{ width: 'auto' }}
showAll={true}
/>
</ScrollView>
);
};
export default ShowAllIndex;
CustomTabView code :
const initialLayout = { width: Dimensions.get('window').width };
const CustomTabView = ({
routes,
renderScene,
numberOfTabs,
indicatorWidth,
scrollEnabled = false,
tabStyle,
showAll,
indicatorStyle,
}) => {
const [index, setIndex] = useState(0);
const renderTabBar = (props) => (
<TabBar
{...props}
scrollEnabled={scrollEnabled}
indicatorStyle={[
indicatorStyle,
{
backgroundColor: colors.barbie_pink,
height: 2.5,
bottom: -1,
},
]}
style={[styles.tabBar]}
renderLabel={({ route, focused }) => {
return (
<Text
style={[styles.label, focused ? styles.activeLabel : styles.label]}
>
{route.title}
</Text>
);
}}
tabStyle={tabStyle}
/>
);
return (
<TabView
navigationState={{ index, routes }}
renderScene={renderScene}
renderTabBar={renderTabBar}
onIndexChange={setIndex}
initialLayout={initialLayout}
style={[styles.container]}
/>
);
};
const styles = StyleSheet.create({
container: {
backgroundColor: colors.dark,
},
scene: {
flex: 1,
marginTop: 5,
},
tabBar: {
backgroundColor: 'transparent',
shadowOpacity: 0,
elevation: 0,
borderBottomWidth: 0.5,
borderColor: colors.very_light_pink_two,
marginBottom: 5,
},
label: {
color: colors.very_light_pink_four,
fontSize: 14,
lineHeight: 20.8,
fontFamily: 'NotoSansCJKkr-Regular',
letterSpacing: -0.35,
},
activeLabel: {
color: colors.barbie_pink,
},
});
Thank you for answers!
Check this solution:
const TAB_MARGIN = 24;
<TabBar
...
scrollEnabled
renderIndicator={indicatorProps => {
const width = indicatorProps.getTabWidth(this.state.index) - TAB_MARGIN
return <TabBarIndicator {...indicatorProps} width={width} />
}}
indicatorStyle={{
backgroundColor: '#333',
height: 4,
left: TAB_MARGIN / 2,
}}
...
/>
I think the prop indicatorStyle in TabBar that can resolves you problem.
You can do the following:
<TabBar
scrollEnabled //add this line to make it scrollable
tabStyle={{width: 100}} //and this one to change the tab width
/>

Multi-color color wheel follow edge problem

I am trying to implement a multi-color color wheel, which lets users drag multiple pickers to change their colors.
The issue here is that, when the user starts dragging one of the pickers and keeps dragging to the edge of the wheel, the dragging gets canceled as soon as the picker hits the edge.
The needed implementation is to keep the dragging going when outside the wheel, but let the picker follow the edge of the wheel until the user lifts the thumb.
I already implemented the outBounds method to detect if the gesture is out of the wheel, but every attempt I did, trying to set the picker to follow the edge using Math.cos and Math.sin has failed.
Any help will be appreciated.
Thanks.
Code:
import React, { Component } from 'react';
import { Animated, Image, Dimensions, PanResponder, StyleSheet, View, Text } from 'react-native';
import colorsys from 'colorsys';
import wheelPng from './color_wheel.png';
import pickerPng from './picker.png';
import colors from '../../../common/colors';
import { isSmallerDevice } from '../../../helpers/layoutFunctions';
class ColorWheel extends Component {
static defaultProps = {
thumbSize: 40,
initialColor: '#ffffff',
onColorChange: () => { },
}
constructor(props) {
super(props)
this.state = {
offset: { x: 0, y: 0 },
currentColor: props.initialColor,
colors: props.colors,
pans: props.colors.map(color => new Animated.ValueXY()),
activeIndex: null,
radius: 0,
renew: false,
spring: new Animated.Value(1)
}
}
static getDerivedStateFromProps(nextProps, prevState) {
let update = { ...prevState };
if (nextProps.colors && nextProps.colors.length && nextProps.colors !== prevState.colors) {
if (nextProps.colors.length > prevState.colors.length) {
update.colors = nextProps.colors;
update.pans = [...prevState.pans, new Animated.ValueXY()];
update.renew = true;
}
}
return update;
}
componentDidUpdate(prevProps, prevState) {
if (this.state.renew) {
this.renewResponders();
this.props.colors.forEach((col, index) => {
this.forceUpdate(col);
});
}
}
componentDidMount = () => {
this.renewResponders();
}
renewResponders = () => {
const { colors } = this.props;
this._panResponders = colors.map((color, index) => this.createResponder(color, index));
this.setState({ renew: false });
}
createResponder = (color, index) => {
const responder = PanResponder.create({
onPanResponderTerminationRequest: () => false,
onStartShouldSetPanResponderCapture: ({ nativeEvent }) => {
this.state.spring.setValue(1.3);
const { onSwiperDisabled } = this.props;
onSwiperDisabled && onSwiperDisabled();
if (this.outBounds(nativeEvent)) return
this.updateColor({ index, nativeEvent })
this.setState({ panHandlerReady: true })
this.state.pans[index].setValue({
x: -this.state.left + nativeEvent.pageX - this.props.thumbSize / 2,
y: -this.state.top + nativeEvent.pageY - this.props.thumbSize / 2 - 40,
})
return true
},
onStartShouldSetPanResponder: (e, gestureState) => true,
onMoveShouldSetPanResponderCapture: () => true,
onMoveShouldSetPanResponder: () => true,
onPanResponderGrant: () => true,
onPanResponderMove: (event, gestureState) => {
this.setState({ activeIndex: index });
if (this.outBounds(gestureState)) return
this.resetPanHandler(index)
return Animated.event(
[
null,
{
dx: this.state.pans[index].x,
dy: this.state.pans[index].y,
},
],
{ listener: (ev) => this.updateColor({ nativeEvent: ev.nativeEvent, index }), useNativeDriver: false },
)(event, gestureState)
},
onPanResponderRelease: ({ nativeEvent }) => {
const { onSwiperEnabled } = this.props;
onSwiperEnabled && onSwiperEnabled();
this.state.pans[index].flattenOffset()
const { radius } = this.calcPolar(nativeEvent)
if (radius < 0.1) {
this.forceUpdate('#ffffff', index)
}
Animated.spring(this.state.spring, {
toValue: 1,
stiffness: 400,
damping: 10,
useNativeDriver: false,
}).start(() => {
this.setState({ panHandlerReady: true, activeIndex: null })
});
if (this.props.onColorChangeComplete) {
this.props.onColorChangeComplete({ index, color: this.state.hsv });
}
},
})
return { color, responder };
}
onLayout() {
setTimeout(() => {
this.self && this.measureOffset()
}, 200);
}
measureOffset() {
/*
* const {x, y, width, height} = nativeEvent.layout
* onlayout values are different than measureInWindow
* x and y are the distances to its previous element
* but in measureInWindow they are relative to the window
*/
this.self.measureInWindow((x, y, width, height) => {
const window = Dimensions.get('window')
const absX = x % width
const radius = Math.min(width, height) / 2
const offset = {
x: absX + width / 2,
y: y % window.height + height / 2,
}
this.setState({
offset,
radius,
height,
width,
top: y % window.height,
left: absX,
});
//
this.forceUpdate(this.state.currentColor)
});
}
calcPolar(gestureState) {
const {
pageX, pageY, moveX, moveY,
} = gestureState
const [x, y] = [pageX || moveX, pageY || moveY]
const [dx, dy] = [x - this.state.offset.x, y - this.state.offset.y]
return {
deg: Math.atan2(dy, dx) * (-180 / Math.PI),
// pitagoras r^2 = x^2 + y^2 normalized
radius: Math.sqrt(dy * dy + dx * dx) / this.state.radius,
}
}
outBounds(gestureState) {
const { radius } = this.calcPolar(gestureState);
return radius > 1
}
resetPanHandler(index) {
if (!this.state.panHandlerReady) {
return
}
this.setState({ panHandlerReady: false })
this.state.pans[index].setOffset({
x: this.state.pans[index].x._value,
y: this.state.pans[index].y._value,
})
this.state.pans[index].setValue({ x: 0, y: 0 })
}
calcCartesian(deg, radius) {
const r = radius * this.state.radius; // was normalized
const rad = Math.PI * deg / 180;
const x = r * Math.cos(rad);
const y = r * Math.sin(rad);
return {
left: this.state.width / 2 + x,
top: this.state.height / 2 - y,
}
}
updateColor = ({ nativeEvent, index }) => {
const { deg, radius } = this.calcPolar(nativeEvent);
const hsv = { h: deg, s: 100 * radius, v: 100 };
this.setState({ hsv });
this.props.onColorChange({ index, color: hsv });
}
forceUpdate = (color, index) => {
const { h, s, v } = colorsys.hex2Hsv(color);
const { left, top } = this.calcCartesian(h, s / 100);
this.props.onColorChange({ color: { h, s, v }, index });
if (index)
this.state.pans[index].setValue({
x: left - this.props.thumbSize / 2,
y: top - this.props.thumbSize / 2,
});
else
this.props.colors.forEach((col, index) => {
this.animatedUpdate(col, index);
});
}
animatedUpdate = (color, index) => {
const { h, s, v } = colorsys.hex2Hsv(color);
const { left, top } = this.calcCartesian(h, s / 100)
// this.setState({ currentColor: color })
// this.props.onColorChange({ h, s, v })
Animated.spring(this.state.pans[index], {
toValue: {
x: left - this.props.thumbSize / 2,
y: top - this.props.thumbSize / 2 - 40,
},
useNativeDriver: false
}).start()
}
render() {
const { radius, activeIndex } = this.state
const thumbStyle = [
styles.circle,
this.props.thumbStyle,
{
position: 'absolute',
width: this.props.thumbSize,
height: this.props.thumbSize,
borderRadius: this.props.thumbSize / 2,
// backgroundColor: this.state.currentColor,
opacity: this.state.offset.x === 0 ? 0 : 1,
flexDirection: 'row',
alignItems: 'center',
alignContent: 'center',
justifyContent: 'center',
},
]
const { colors } = this.props;
// const panHandlers = this._panResponder && this._panResponder.panHandlers || {}
return (
<View
ref={node => {
this.self = node
}}
onLayout={nativeEvent => this.onLayout(nativeEvent)}
style={[styles.coverResponder, this.props.style]}>
{!!radius && <Image
style={[styles.img,
{
height: radius * 2,
width: radius * 2
}]}
source={wheelPng}
/>}
{colors && colors.map((color, index) =>
<Animated.View key={index} style={[this.state.pans[index].getLayout(), thumbStyle, { zIndex: activeIndex === index ? 9 : 3, transform: [{ scale: activeIndex === index ? this.state.spring : 1 }] }]} {...this._panResponders && this._panResponders[index] && this._panResponders[index].responder.panHandlers}>
<Animated.Image
style={[
{
height: this.props.thumbSize * 2,
width: this.props.thumbSize * 2,
resizeMode: 'contain',
position: 'absolute',
tintColor: '#000000'
}]}
source={pickerPng}
/>
<Animated.View style={[styles.circle, {
position: 'absolute',
top: -8,
left: 2,
width: this.props.thumbSize,
height: this.props.thumbSize,
borderRadius: this.props.thumbSize / 2,
backgroundColor: color,
opacity: this.state.offset.x === 0 ? 0 : 1,
flexDirection: 'row',
alignItems: 'center',
alignContent: 'center',
justifyContent: 'center'
}]} >
<Text style={isSmallerDevice ? styles.smallerDeviceCountText : styles.countText}>{index + 1}</Text>
</Animated.View>
</Animated.View>
)}
</View>
)
}
}
const styles = StyleSheet.create({
coverResponder: {
flex: 1,
alignItems: 'center',
justifyContent: 'center'
},
img: {
alignSelf: 'center',
},
circle: {
position: 'absolute',
backgroundColor: '#000000',
// borderWidth: 3,
// borderColor: '#EEEEEE',
elevation: 3,
shadowColor: 'rgb(46, 48, 58)',
shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.8,
shadowRadius: 2,
},
countText: {
flex: 1,
textAlign: 'center',
fontFamily: 'Rubik-Bold',
fontSize: 20,
color: colors.titleMain
},
smallerDeviceCountText: {
flex: 1,
textAlign: 'center',
fontFamily: 'Rubik-Bold',
fontSize: 16,
color: colors.titleMain
}
})
export default ColorWheel;

React material-table: Color rows in data-tree

I am using material-table in my React project. I have 3 levels of the data tree. Here it is the first one:
Is it possible when I click the first of the 2 items on 1st Level in data tree table to color it so it would be easier to see that values under it are child elements. Like this:
Also I would be even happier if it is possible to color it when I am passing data to it. Here it is how I am passing data:
data={[
{
id: 1, // MAIN ELEMENT
name: "Parent",
value: `Parent`,
},
{
id: 2, //CHILD OF THE MAIN ELEMENT
name: "Child",
value: `Child`,
parentId: 1,
}]}
Is there an option to color parent Element even before opening it, so it would be clear that it is PARENT and other is CHILD?
UPDATE:
Here is codesandbox example. As you can see when you open Parent1 Parent2 seems to be under Parent1. I want to make it clear that it is NOT under it.
https://codesandbox.io/s/jolly-germain-6fncr?file=/src/App.js
Let we talk about this problem first. It's neither programmatic nor css problem. It's just the problem how you show data, in other words, UX only.
There are several ways to achive, this is my work example: https://codesandbox.io/s/withered-dust-hb882?file=/src/App.js
Basically I just add one first column for parent only, that's it.
Ok, using CSS selectors it is not so easy to implement onExapnd color change. Here you will have to write check for parent TR check and sub Button rotate(90deg) check. To change the colors without onClick check you can use the following CSS:
tr[level="0"] {
background-color: #FF0000;
}
tr[level="1"] {
background-color: #FF0033;
}
tr[level="2"] {
background-color: #FF0066;
}
In JS way you can use the following code (of course you will have to add it in every table or extend the table or use util lib with ready rowStyle method..)
import React from "react";
import MaterialTable from "material-table";
import SearchIcon from "#material-ui/icons/Search";
import RotateLeftIcon from "#material-ui/icons/RotateLeft";
import { ArrowUpward, ChevronRight } from "#material-ui/icons";
//import './styles.css';
export default () => {
const constPathColors = {
1: '#FFFF00',
2: '#FFFF33',
3: '#FFFF66',
4: '#FFFF99',
5: '#FFFFCC'
};
return (
<MaterialTable
style={{ width: "100%", margin: "3%" }}
title="Income Statement"
icons={{
Filter: React.forwardRef((props, ref) => <SearchIcon ref={ref} />),
Search: React.forwardRef((props, ref) => <SearchIcon ref={ref} />),
ResetSearch: React.forwardRef((props, ref) => (
<RotateLeftIcon ref={ref} />
)),
SortArrow: ArrowUpward,
DetailPanel: ChevronRight
}}
columns={[
{
field: "name",
title: "Category"
},
{
field: "value",
title: "Value",
cellStyle: {
textAlign: "center"
}
}
]}
data={[
{
id: 1, // MAIN ELEMENT
name: "Parent 1",
value: `SomeParentValue`
},
{
id: 2, //CHILD OF THE MAIN ELEMENT
name: "Child 1-1",
value: `Child Value`,
parentId: 1
},
{
id: 3, //CHILD OF THE MAIN ELEMENT
name: "Child 1-2",
value: `Child Value`,
parentId: 1
},
{
id: 4, //CHILD OF THE CHILD ELEMENT
name: "Child 1-2-1",
value: `Child Value`,
parentId: 3
},
{
id: 5, // MAIN ELEMENT
name: "Parent 2",
value: `SomeParentValue`
}
]}
parentChildData={(row, rows) => rows.find(a => a.id === row.parentId)}
options={{
paging: false,
headerStyle: {
backgroundColor: "#378FC3",
color: "#FFF",
fontSize: "17px",
textAlign: "center",
fontWeight: "bold"
},
rowStyle: rowData => {
if(rowData.tableData.isTreeExpanded === false && rowData.tableData.path.length === 1) {
return {};
}
const rowBackgroundColor = constPathColors[rowData.tableData.path.length];
return {backgroundColor: rowBackgroundColor};
}
}}
/>
);
};
The row has default color before the expanding:
After expanding it has yellow (gradient depend on level) background color:
Thats how my tree view looks like. Thanks to the left: `var(--left-before, ${0}px), i could positioning the :befores wherever i want
https://i.ibb.co/Wp9XJcc/childscapture.png
viewTableTree.styles.js
import { makeStyles } from '#material-ui/core/styles';
export const useViewTableTreeStyles = makeStyles(theme => ({
root: {
'& .MuiPaper-root': {
boxShadow: 'none'
},
'& .MuiTable-root': {
position: 'relative',
overflow: 'hidden'
},
'& .MuiTableRow-root': {
'&:hover': { backgroundColor: 'rgba(0, 0, 0, 0.04)' },
'&:before': {
content: '""',
fontWeight: theme.font.weight.black,
fontSize: theme.font.size.xxl,
position: 'absolute',
left: `var(--left-before, ${0}px)`, //important trick here!
width: '1px',
height: '3.2rem',
backgroundColor: theme.palette.basic.bright
}
}
}
}));
then in the MaterialTable component
ViewTableTree.js
<div className={classes.root}>
<MaterialTable
icons={tableIcons}
data={rows}
columns={cells}
localization={{
header: {
actions: ''
}
}}
options={{
selection: false,
paging: false,
search: false,
showTitle: false,
toolbar: false,
actionsColumnIndex: -1,
rowStyle: rowData => {
let styles = { transition: 'transform 300ms' };
const levels = rowData.tableData.path.length === 1 ? 0 : rowData.tableData.path.length;
styles = { ...styles, '--left-before': `${levels * 6}px` };
return rowData.tableData.isTreeExpanded
? {
...styles,
fontWeight: 600,
backgroundColor: 'rgba(77, 93, 241, 0.08)'
}
: styles;
}
}}
{...props}
/>
</div>

Categories