Adding Padding with React Konva - konvajs

I want to create the equivalent of this (sudo code):
<div padding="4px">
<p>My Text</p>
</div>
With React Konva elements. I know how to start, using Group, Rect, and Text, but I can't figure out how to do the padding. Hope someone can help! Thanks!!
EDIT:
This is what I am trying to build (the green background with 2px padding around the text).

You have padding property for text shape to use it.
In order to wrap a rectangle around a text shape, you have to calculate the size of the text.
const App = () => {
const textRef = React.useRef();
const [size, setSize] = React.useState({ x: 0, y: 0 });
React.useEffect(() => {
setSize({
width: textRef.current.width(),
height: textRef.current.height()
});
}, []);
return (
<Stage width={window.innerWidth} height={window.innerHeight}>
<Layer>
<Group x={20} y={50}>
<Rect
width={size.width}
height={size.height}
fill="red"
shadowBlur={10}
/>
<Text
text="Some text on canvas"
ref={textRef}
fontSize={15}
padding={10}
/>
</Group>
</Layer>
</Stage>
);
};
https://codesandbox.io/s/react-konva-text-with-padding-yh876

Related

How to set the initial dimensions of a transformer when applying it on a group having clip properties in Konva?

I have a Konva.Group with a few nodes to which I have set clip properties to limit what is seen. I'm applying Konva.Transformer to the group and the problem I'm facing is that the Transformer encloses the entire group, even the unclipped portion. This is not looking good even if it is fully functional and does the job. Is there any way to set the initial width and height of the transformer so that it only encloses the clipped portion?
This is the group before applying clip and transform
This is what it looks like after applying clip and transform
import React, {useRef, useEffect} from 'react';
import { render } from 'react-dom';
import { Stage, Layer, Rect, Circle, Line, Group, Transformer } from 'react-konva';
const App = () => {
const trRef = useRef(null)
const grpRef = useRef(null)
useEffect(()=>{
const transformNode = trRef.current;
transformNode.enabledAnchors(["top-left",
"top-right",
"bottom-left",
"bottom-right"])
transformNode.nodes([grpRef.current])
},[trRef])
return (
<Stage width={window.innerWidth} height={window.innerHeight}>
<Layer>
<Group ref={grpRef} clipX={0} clipY={0} clipWidth={200} clipHeight={200}>
<Rect
x={20}
y={50}
width={100}
height={100}
fill="red"
shadowBlur={10}
/>
<Circle x={200} y={100} radius={50} fill="green" />
<Line
x={20}
y={200}
points={[0, 0, 100, 0, 100, 100]}
tension={0.5}
closed
stroke="black"
fillLinearGradientStartPoint={{ x: -50, y: -50 }}
fillLinearGradientEndPoint={{ x: 50, y: 50 }}
fillLinearGradientColorStops={[0, 'red', 1, 'yellow']}
/>
</Group>
<Transformer rotateEnabled={false} ref={trRef} />
</Layer>
</Stage>
);
};
render(<App />, document.getElementById('root'));
This is the default behavior of Konva.Transform and it can't be altered. One workaround would be to create a transparent rectangle around the clipped portion, apply the transform to it and then copy the changes to the group.
import React, {useRef, useEffect} from 'react';
import { render } from 'react-dom';
import { Stage, Layer, Rect, Circle, Line, Group, Transformer } from 'react-konva';
const App = () => {
const trRef = useRef(null)
const grpRef = useRef(null)
const rectRef = useRef(null)
// To copy the transform matrix from the rectangle to the group
function handleTransform(e){
const shape1 = e.target;
const transform = shape1.getTransform().copy();
const attrs = transform.decompose();
grpRef.current.setAttrs(attrs);
}
useEffect(()=>{
const transformNode = trRef.current;
transformNode.enabledAnchors(["top-left",
"top-right",
"bottom-left",
"bottom-right"])
transformNode.nodes([rectRef.current])
},[trRef])
return (
<Stage width={window.innerWidth} height={window.innerHeight}>
<Layer>
<Group draggable>
{/* Transparent rectangle to which the transform is now applied to */}
<Rect
ref={rectRef}
x={0}
y={0}
width={200}
height={200}
id="invisible-rect"
/>
<Group ref={grpRef} clipX={0} clipY={0} clipWidth={200} clipHeight={200}>
<Rect
x={20}
y={50}
width={100}
height={100}
fill="red"
shadowBlur={10}
/>
<Circle x={200} y={100} radius={50} fill="green" />
<Line
x={20}
y={200}
points={[0, 0, 100, 0, 100, 100]}
tension={0.5}
closed
stroke="black"
fillLinearGradientStartPoint={{ x: -50, y: -50 }}
fillLinearGradientEndPoint={{ x: 50, y: 50 }}
fillLinearGradientColorStops={[0, 'red', 1, 'yellow']}
/>
</Group>
</Group>
<Transformer onTransform={handleTransform} rotateEnabled={false} ref={trRef} />
</Layer>
</Stage>
);
};
render(<App />, document.getElementById('root'));
Here's the demo of the above code. Thanks to Anton, the creator of this wonderful library for suggesting this solution.
Reference - Konva shape transform sharing is simple

Is there a way for a TextInput to behave like a button i.e. be able to be selected (onPress)?

I am making a react native health app in which the user can select tags to describe certain symptoms they are facing. I am trying to let the user create their own tags, at the moment the user can enter tags but cannot select them. Is there a way to allow them to select text inputs?
I have already tried wrapping touchable opacity around it but when I press the text input the cursor just focuses on the word (wanting me to edit the word).
I have also tried editable = {false} this removes the ability for the user to enter a text input completely. Is there a way to allow the user to input a value once and then disable the text input (non-editable)?
Or If I used Button instead of TextInput is there a way for the user to enter the title of the button so it can act as a tag?
Here is how I have allowed users to create text inputs
addTextInput = (index) => {
let textInput = this.state.textInput;
textInput.push(
<TextInput
style={styles.textInput}
onChangeText={(text) => this.addValues(text, index)}
editable={true}
/>
);
this.setState({ textInput });
}
removeTextInput = () => {
let textInput = this.state.textInput;
let inputData = this.state.inputData;
textInput.pop();
inputData.pop();
this.setState({ textInput, inputData });
}
and this is what my current tags look like:
on the picture when the user presses the plus a new tag/TextInput is created, what I want is when the user presses it, it should be able to change color or the like.
here is the code for the plus button:
<View style={{
flexDirection: 'row',
flexGrow: '1',
flexWrap: 'wrap',
width: Responsive.width(300)
}}>
{this.state.textInput.map((value) => {
return value
})}
<TouchableWithoutFeedback onPress={() => {
this.addTextInput(this.state.textInput.length)
}}>
<Image
style={{ marginLeft: 8, width: 38, height: 38 }}
source={require('../../../assets/plusButton.png')}
/>
</TouchableWithoutFeedback>
{/* <Button title='Get Values' onPress={() => this.getValues()} /> */}
</View>
<View style={styles.row}>
<View style={{ margin: 10, top: Responsive.height(75) }}>
<Button onPress={() => this.removeTextInput()}>Remove</Button>
</View>
</View>
I had a similar problem and solved it with a TouchableOpacity over a TextInput. Using zIndex and absolute positioning you can expand the TouchableOpacity over your text input and transform it in a button, the TouchableOpacity must have a 100% opacity.
You can control with { condition && } your logic to make it behave like a button or a TextInput.
export default function App() {
const [m,setm]=React.useState(false);
return (
<View>
<TouchableOpacity style={{zIndex:10, position:'absolute', width: '100%', height: '100%', opacity:'100%'}} onPress={()=>{setm(!m)}} />
<TextInput style={{zIndex:1,backgroundColor:'#fbb',color:'#000'}} value={m}/>
</View>
);
}

How to use KeyboardAvoidingView with FlatList?

I have a FlatList component with an Input inside each row. When I select the input I want it to scroll up above the keyboard.
My code:
return (
<KeyboardAvoidingView behavior='padding' style={{ flex: 1 }} >
<FlatList
style={{ flex: 1, backgroundColor: '#fff' }}
data={ds}
renderItem={({ item }) => <ListItem data={item} />}
ListFooterComponent={this.renderButton}
/>
</KeyboardAvoidingView>
);
In this scenario, the FlatList is never loaded. When I delete flex:1 from both components, FlatList renders properly but selecting an Input does not make it scroll up
You can trying using react-native-keyboard-aware-scroll-view
https://github.com/APSL/react-native-keyboard-aware-scroll-view
It comes with KeyboardAware[ScrollView, ListView, SectionView, FlatList] which accepts the same props as their corresponding components from RN. I have used that and it worked for me.
render() {
return (
<KeyboardAwareFlatList
style={{flex: 1}}
data={this.state.data}
renderItem={({item}) => (
<View style={{flex: 1}}>
<Image
source={item.v}
style={{height:200, width: 200}}
/>
<TextInput
placeholder="enter text1"
/>
</View>
)}
/>
);
}
You could try using the library react-native-keyboard-spacer as an alternative to KeyboardAvoidingView.
Install:
npm install --save react-native-keyboard-spacer
Use it like this:
import KeyboardSpacer from 'react-native-keyboard-spacer'
...
<View style={{flex: 1}}>
<FlatList
style={{flex: 1}}
data={ds}
renderItem={({ item }) => <ListItem data={item} />}
/>
{/* The view that will expand to match the keyboard height */}
<KeyboardSpacer />
</View>
Try this:
<KeyboardAvoidingView behavior='position' keyboardVerticalOffset={xyz} >
You can remove the property 'keyboardVerticalOffset' or play with the value of xyz,
just find out the better value which fits in your case.
For anyone on a similar path as mine. I was not able to use KeyboardAvoidingView because it depends on ScrollView which conflicts with Flatlist. I couldn't used the header and footer option in Flatlist as I'm using it as a generated thing in a search selection box so it has to be contained.
For me there is a difference in how Android and iOS calculate absolute position. Android considers the bottom to be the top of the keyboard and iOS it is the bottom of the screen when the keyboard is showing.
It turns out to be not that difficult to just put a View around the content you want to remain above the keyboard and just dynamically set the height of it on iOS. This isn't even really necessary on Android as it follows the keyboard if the View is position: absolute and bottom: 0.
This heavily borrows from here: https://stackoverflow.com/a/60682069/438322
Thanks to Kevin Amiranoff
Here's a basic example using hooks.
function YourComponent(props){
const onKeyboardWillShow = e => {
setKeyboardHeight(e.endCoordinates.height);
};
const onKeyboardWillHide = () => {
setKeyboardHeight(0);
};
useEffect(() => {
// These listeners on ios are a little more snappy but not available on Android
// If you want to use this on Android use keyboardDidShow/Hide
if (Platform.OS === 'ios') {
Keyboard.addListener('keyboardWillShow', onKeyboardWillShow);
Keyboard.addListener('keyboardWillHide', onKeyboardWillHide);
}
return () => {
if (Platform.OS === 'ios') {
Keyboard.removeListener('keyboardWillShow', onKeyboardWillShow);
Keyboard.removeListener('keyboardWillHide', onKeyboardWillHide);
}
};
}, []);
const buttonHeight = 50;
return(
<View>
<Content bla={'bla'}/>
<View style={{
height: Platform.OS === 'ios'
? keyboardHeight + buttonHeight : buttonHeight,
position: 'absolute',
bottom: 0
}}>
{/* Keep this button above the keyboard */}
<Button style={{ height: buttonHeight }}/>
</View
</View>
)
}
this is my solution.
inverted={true} is the key
const dummy = [1,2,3,4,5,6,7,8,9,10]
<KeyboardAvoidingView >
<FlatList
data={dummy.reverse()}
inverted={true}
/>
</KeyboardAvoidingView>

Why isn't zIndex working in React Native?

I've been trying to use the property zIndex on a ScrollView but it doesn't seem to be working. When I apply zIndex it looks like this:
As you can see, the ScrollView is being blocked by the input. I want the effect of having the ScrollView completely cover the other input when the suggestions come up, with zIndex. Here's what I have right now:
<View>
<Input
value={this.state.inputValue}
style={styles._input}
inputStyle={styles._text}
onChangeText={this.handleChangeText}
{...this.props}
/>
<View style={styles._spacing}></View>
{predictions.length ?
<ScrollView
style={styles._scroll}
>
{predictions.map(this.renderPrediction)}
</ScrollView>
: null}
</View>
Note that Input is a separate component that renders a specific TextInput. Also, this.renderPrediction returns this:
<Text
style={styles._text}
key={index}
onPress={this.handlePredictionPress.bind(null, prediction.description)}
>
{prediction.description}
</Text>
And finally here are my styles:
const styles = EStyleSheet.create({
input: StyleSheet.flatten([inputStyle]),
text: {
fontSize: 15,
},
spacing: {
height: 10
},
scroll: {
position: "absolute",
zIndex: 10,
width: "80%",
top: 50,
backgroundColor: "white"
}
});
Why isn't my zIndex attempt working and how can I get it to work as desired?
According to your image it looks like the zIndex is working as expected, the ScrollView is over the input. But the white background didn't catch.
Use contentContainerStyle for the background color behind the items.
<ScrollView style={styles._scroll} contentContainerStyle={styles._contentContainer} >
...
contentContainer: {backgroundColor: "white"}

Center Align contents of a ListView in react native

I have used a ListView to list some items but they're all left aligned. How to center align them without using another View wrapping the ListView?
Code for the ListView
<ListView
dataSource={this.state.dataSource}
renderRow={this.renderItem}
style={styles.listView}
/>
ListView Styling
listView: {
paddingTop: 20,
paddingBottom: 20,
}
How to do this? Thanks in advance.
You can solve this by using the contentContainerStyle ListView prop to apply the needed flexbox styles directly to the ListView's internal container, like so:
<ListView
dataSource={this.state.dataSource}
renderRow={this.renderItem}
contentContainerStyle={styles.listView}
/>
// ...
var styles = StyleSheet.create({
listView: {
flex: 1,
justifyContent: 'center',
},
});
Likewise, this also works when you need both axes centered by adding alignItems: 'center' to the listView style.
this.renderItem should be a function which renders the row. Your row should set the styles it needs. So something like this:
<ListView
dataSource={this.state.dataSource}
renderRow={this.renderItem}
style={styles.listView}
/>
renderItem: (item) {
<ItemRow item={item} />
}
And then in the ItemRow component:
render: () {
<View style={flexDirection: 'row', justifyContent: 'center'}>
<Text>{this.props.item.name</Text>
</View>
}

Resources