Cypress is reporting the wrong size on element - automated-tests

I'm using cypress to test our pattern library elements. The alert pattern the <p> set to font-size: 14px. When I inspect the CSS and the dev inspector, I see the correct size being rendered.
But cypress is reporting 16.8px. To double check, I asked cypress to log how high the .alert element is. The cy.log reports a height of 34px which is correct. The inspector reports a height of 60px. What is going on here?
```
/// <reference types="Cypress" />
context('AlertButtonClose', () => {
it('Button close displays as designed', () => {
cy.visit('?p=molecules-alerts')
cy.get('#sg-viewport').then(function ($iframe) {
const $jbody = $iframe.contents().find('body')
// Alert Type - All
const $alertAll = $jbody.find('.alert')
cy.screenshot()
cy.log('.alert height: ' + $alertAll.height())
cy.wrap($alertAll)
.should('exist', 'be.visible')
.should('have.css', 'min-height').and('gte', '3.125rem')
cy.wrap($alertAll).find('p')
.should('exist', 'be.visible')
.should('have.css', 'font-size', '14px')
cy.viewport(639, 720)
cy.wrap($alertAll).find('.alert-link')
.should('not.be.visible')
cy.viewport(960, 720)
cy.wrap($alertAll).find('.alert-link')
.should('exist', 'be.visible')
.should('have.css', 'font-weight', '300', 'text-transform', 'uppercase', 'font-size').should('gte', '10.5px')
if ($alertAll) {
expect($alertAll)
.to.have.css('color', 'rgb(255, 255, 255)')
}
// Alert Type - Info
const $alertInfo = $jbody.find('.alert-info')
if ($alertInfo) {
expect($alertInfo)
.to.have.css('background-color', 'rgb(3, 155, 229)')
}
// Alerts Type - Dismissable
const $alertDismissable = $jbody.find('.alert-dismissable')
if ($alertDismissable) {
cy.wrap($jbody).find('.close').should('exist', 'be.visible')
cy.wrap($jbody).find('.close img')
.should('have.css', 'height', '15px')
.should('have.css', 'width', '15px')
.should('have.attr', 'alt').should('not.be.empty')
cy.wrap($jbody).find('.close img')
.should('have.attr', 'src').should('include', 'close-alert.svg')
cy.wrap($jbody).find('.close').click()
cy.wrap($jbody).find('.alert-dismissable').should('not.exist')
}
})
})
})
```
Additionally, the image generated by `cy.screenshot() is crazy over-sized! The alert bar appears to be over 80px in height which utter nonsense.

I don't think this is a bug per se. We are taking an approach to responsive design where we're changing the base-font size on the body tag. It seems as if Cypress is unable to recalculate based on breakpoint in the manner we've implemented:
html {
font-size: $font-size-base; //16px
#media (min-width: 640px) {
font-size: $font-size-base-md; //18px
}
#media (min-width: 960px) {
font-size: $font-size-base-lg; //20px
}
}
li, p {
#include font-size(14)
}
For such a small pattern, the test has morphed into a rather complex exercise:
///
describe('Alert Close Button', () => {
context('iphone-5 resolution', () => {
beforeEach(function () {
cy.viewport('iphone-5')
})
it('GLOBAL & XS breakpoint: Alert displays as designed', () => {
cy.visit('?p=molecules-alerts')
cy.get('#sg-viewport').then(function ($iframe) {
const $jbody = $iframe.contents().find('body')
// Alert Type - All
const $alertAll = $jbody.find('.alert')
cy.log("alert height: " + $alertAll.height())
cy.wrap($alertAll)
.should('exist', 'be.visible')
.should('have.css', 'min-height').should('be.gte', '50px')
cy.wrap($alertAll).find('p')
.should('exist', 'be.visible')
.should('have.css', 'font-size', '14px', 'font-weight', '800', 'line-height', 'calc(14 / 18)', 'min-height', '2.6rem', )
cy.wrap($alertAll).find('.alert-link')
.should('not.be.visible')
if ($alertAll) {
expect($alertAll)
.to.have.css('color', 'rgb(255, 255, 255)')
.to.have.css('padding', '.4375rem 3.125vw')
}
// Alert Type - Info
const $alertInfo = $jbody.find('.alert-info')
if ($alertInfo) {
expect($alertInfo)
.to.have.css('background-color', 'rgb(3, 155, 229)')
}
// Alerts Type - Dismissable
const $alertDismissable = $jbody.find('.alert-dismissable')
if ($alertDismissable) {
cy.wrap($jbody).find('.close').should('exist', 'be.visible')
cy.wrap($jbody).find('.close img')
.should('have.css', 'height', '15px')
.should('have.css', 'width', '15px')
.should('have.attr', 'alt').should('not.be.empty')
cy.wrap($jbody).find('.close img')
.should('have.attr', 'src').should('include', 'close-alert.svg')
cy.wrap($jbody).find('.close').click()
cy.wrap($jbody).find('.alert-dismissable').should('not.exist')
}
})
})
})
context('ipad-2', () => {
beforeEach(function () {
cy.viewport('ipad-2')
})
it('SM breakpoint: Alert displays correctly', () => {
cy.visit('?p=molecules-alerts')
cy.get('#sg-viewport').then(function ($iframe) {
const $jbody = $iframe.contents().find('body')
// Alert Type - All
const $alertAll = $jbody.find('.alert')
if ($alertAll) {
expect($alertAll)
.to.have.css('color', 'rgb(255, 255, 255)')
.to.have.css('padding', '.75rem 3.125vw')
}
})
})
})
context('tablet-mixed', () => {
beforeEach(function () {
cy.viewport(960, 600)
})
it('MD breakpoint: Alert displays correctly', () => {
cy.visit('?p=molecules-alerts')
cy.get('#sg-viewport').then(function ($iframe) {
const $jbody = $iframe.contents().find('body')
// Alert Type - All
const $alertAll = $jbody.find('.alert')
cy.wrap($alertAll)
.should('exist', 'be.visible')
.should('have.css', 'min-height').and('gte', '2.625rem')
cy.wrap($alertAll).find('p')
.should('exist', 'be.visible')
.should('have.css', 'font-size', '16px')
cy.wrap($alertAll).find('.alert-link')
.should('exist', 'be.visible')
.should('have.css', 'font-weight', '300', 'text-transform', 'uppercase', 'font-size', '10.5px')
})
})
})
})

Related

MapboxglSpiderifier in NextJs (react-map-gl)

I am using Map from react-map-gl to display several markers in a map. I also use Layers and Source to have my markers in clusters. But I have a problem because I can have multiple points with the exact same coordinate and those points end up overlaying each other when zooming in (it seems that there is only one marker in the position when there are multiple).
I have installed mapboxgl-spiderifier to overcome this problem but I can't seem to get it working.
Here's some of my code:
const [spiderifier, setSpiderifier] = useState(null);
const ref = useRef();
...
const onMapLoad = React.useCallback(() => {
setSpiderifier(
new MapboxglSpiderifier(ref.current.getMap(), {
onClick: function (e, spiderLeg) {
e.stopPropagation();
console.log("Clicked on ", spiderLeg);
},
markerWidth: 100,
markerHeight: 100,
})
);
}, []);
const onClick = (event) => {
const feature = event.features[0];
if (feature.layer.id === "clusters") {
const clusterId = feature.properties.cluster_id;
const mapboxSource = ref.current.getSource("classesandtrainers");
if (location.zoom >= 12) {
mapboxSource.getClusterLeaves(
clusterId,
100,
0,
function (err, leafFeatures) {
if (err) {
return console.error("error while getting leaves of a cluster", err);
}
let markers = leafFeatures.map((leafFeature) => {
return leafFeature.properties;
});
spiderifier.spiderfy(event.lngLat, { ...geoJson, features: markers });
}
);
return;
}
mapboxSource.getClusterExpansionZoom(clusterId, (err, zoom) => {
if (err) {
return;
}
ref.current.easeTo({
center: feature.geometry.coordinates,
zoom,
duration: 500,
});
});
}
};
...
return (
<Map
ref={ref}
{...location}
onLoad={onMapLoad}
onMove={(evt) => setLocation(evt.viewState)}
onClick={onClick}
mapStyle="mapbox://styles/flxbl/cl92sjxf4001g15la7upwjij2"
mapboxAccessToken={process.env.mapbox_key}
style={{ width: "100%", height: "100%", margin: 0, padding: 0 }}
interactiveLayerIds={[clusterLayer.id, unclusteredPointLayer.id]}
>
<Source
id="classesandtrainers"
type="geojson"
data={sourceData()}
cluster={true}
clusterMaxZoom={14}
clusterRadius={50}
>
<Layer {...clusterLayer} />
<Layer {...clusterCountLayer} />
<Layer {...unclusteredPointLayer} />
</Source>
<ScaleControl position="bottom-right" />
{renderPopup()}
</Map>
Can someone please help me?

How to do video chat using webrtc in angular8

Hello I am trying to integrate video chat between two users in angular 8 Using webRTC but I am unable to do that.Is there any suggestion how will i do that in angula8 please help me out.
component.html
<div class="videoCall">
<video #localVideo class="self" playsinline autoplay></video>
<video #remoteVideo class="self" style="margin-left: 10px;" playsinline autoplay></video>
</div>
<div>
<button #startButton [disabled]="startButtonDisabled" (click)="start()">Start</button>
<button #callButton [disabled]="callButtonDisabled" (click)="call()">Call</button>
<button #hangupButton [disabled]="hangupButtonDisabled" (click)="hangup()">Hang Up</button>
</div>
componenet.ts
import { Component, OnInit, AfterViewInit, ViewChild, ElementRef } from '#angular/core';
import * as signalR from "#aspnet/signalr";
import { VideoCallService } from '../../common/services/video-call.service';
// import{ConnectionManager} from '../right-connect/connectionManager.component';
import adapter from 'webrtc-adapter';
#Component({
selector: 'right-connect',
templateUrl: './right-connect.component.html',
styleUrls: ['./right-connect.component.scss']
})
export class RightConnectComponent implements AfterViewInit {
#ViewChild('startButton', {static: false}) startButton: ElementRef;
#ViewChild('callButton', {static: false}) callButton: ElementRef;
#ViewChild('hangupButton', {static: false}) hangupButton: ElementRef;
#ViewChild('localVideo', {static: false}) localVideo: ElementRef;
#ViewChild('remoteVideo', {static: false}) remoteVideo: ElementRef;
startButtonDisabled = false;
callButtonDisabled = true;
hangupButtonDisabled = true;
startTime;
localStream;
pc1;
pc2;
offerOptions = {
offerToReceiveAudio: 1,
offerToReceiveVideo: 1
};
ngAfterViewInit() {
this.localVideo.nativeElement.addEventListener('loadedmetadata', ()=> {
this.trace('Local video videoWidth: ' + this.localVideo.nativeElement.videoWidth +
'px, videoHeight: ' + this.localVideo.nativeElement.videoHeight + 'px');
});
this.remoteVideo.nativeElement.addEventListener('loadedmetadata', ()=> {
this.trace('Remote video videoWidth: ' + this.remoteVideo.nativeElement.videoWidth +
'px, videoHeight: ' + this.remoteVideo.nativeElement.videoHeight + 'px');
});
this.remoteVideo.nativeElement.onresize = ()=> {
this.trace('Remote video size changed to ' +
this.remoteVideo.nativeElement.videoWidth + 'x' + this.remoteVideo.nativeElement.videoHeight);
};
}
getName(pc) {
return (pc === this.pc1) ? 'pc1' : 'pc2';
}
getOtherPc(pc) {
return (pc === this.pc1) ? this.pc2 : this.pc1;
}
gotStream(stream) {
this.trace('Received local stream');
this.localVideo.nativeElement.srcObject = stream;
this.localStream = stream;
this.callButtonDisabled = false;
}
start() {
this.trace('Requesting local stream');
this.startButtonDisabled = true;
navigator.mediaDevices.getUserMedia({
audio: true,
video: true
})
.then(this.gotStream.bind(this))
.catch(function(e) {
console.log('error', e);
alert('getUserMedia() error: ' + e.name);
});
}
call() {
this.callButtonDisabled = true;
this.hangupButtonDisabled = false;
this.trace('Starting call');
this.startTime = window.performance.now();
var videoTracks = this.localStream.getVideoTracks();
var audioTracks = this.localStream.getAudioTracks();
if (videoTracks.length > 0) {
this.trace('Using video device: ' + videoTracks[0].label);
}
if (audioTracks.length > 0) {
this.trace('Using audio device: ' + audioTracks[0].label);
}
var servers = {'iceServers': [{'urls': 'stun:stun.l.google.com:19302'}]};
this.pc1 = new RTCPeerConnection(servers);
this.trace('Created local peer connection object pc1');
/*this.pc1.onicecandidate = e => {
this.onIceCandidate(this.pc1, e);
};*/
this.pc1.addEventListener('icecandidate', (e: Event) => {
this.onIceCandidate(this.pc1, e);
});
this.pc2 = new RTCPeerConnection(servers);
this.trace('Created remote peer connection object pc2');
/*this.pc2.onicecandidate = e => {
this.onIceCandidate(this.pc2, e);
};*/
this.pc2.addEventListener('icecandidate', (e: Event) => {
this.onIceCandidate(this.pc2, e);
});
/*this.pc1.oniceconnectionstatechange = e => {
this.onIceStateChange(this.pc1, e);
};
this.pc2.oniceconnectionstatechange = e => {
this.onIceStateChange(this.pc2, e);
};*/
this.pc1.addEventListener('iceconnectionstatechange', (e: Event) => {
this.onIceStateChange(this.pc1, e);
});
this.pc2.addEventListener('iceconnectionstatechange', (e: Event) => {
this.onIceStateChange(this.pc2, e);
});
this.pc2.ontrack = this.gotRemoteStream.bind(this);
this.localStream.getTracks().forEach(
track => {
this.trace('add tracks to pc1');
this.pc1.addTrack(
track,
this.localStream
);
}
);
this.trace('Added local stream to pc1');
this.trace('pc1 createOffer start');
this.pc1.createOffer(
this.offerOptions
).then(
this.onCreateOfferSuccess.bind(this),
this.onCreateSessionDescriptionError.bind(this)
);
}
onCreateSessionDescriptionError(error) {
this.trace('Failed to create session description: ' + error.toString());
}
onCreateOfferSuccess(desc) {
this.trace('Offer from pc1\n' + desc.sdp);
this.trace('pc1 setLocalDescription start');
this.pc1.setLocalDescription(desc).then(
() => {
this.onSetLocalSuccess(this.pc1);
},
this.onSetSessionDescriptionError.bind(this)
);
this.trace('pc2 setRemoteDescription start');
this.pc2.setRemoteDescription(desc).then(
() => {
this.onSetRemoteSuccess(this.pc2);
},
this.onSetSessionDescriptionError.bind(this)
);
this.trace('pc2 createAnswer start');
// Since the 'remote' side has no media stream we need
// to pass in the right constraints in order for it to
// accept the incoming offer of audio and video.
this.pc2.createAnswer().then(
this.onCreateAnswerSuccess.bind(this),
this.onCreateSessionDescriptionError.bind(this)
);
}
onSetLocalSuccess(pc) {
this.trace(this.getName(pc) + ' setLocalDescription complete');
}
onSetRemoteSuccess(pc) {
this.trace(this.getName(pc) + ' setRemoteDescription complete');
}
onSetSessionDescriptionError(error) {
this.trace('Failed to set session description: ' + error.toString());
}
gotRemoteStream(e) {
if (this.remoteVideo.nativeElement.srcObject !== e.streams[0]) {
this.remoteVideo.nativeElement.srcObject = e.streams[0];
this.trace('pc2 received remote stream');
}
}
onCreateAnswerSuccess(desc) {
this.trace('Answer from pc2:\n' + desc.sdp);
this.trace('pc2 setLocalDescription start');
this.pc2.setLocalDescription(desc).then(
() => {
this.onSetLocalSuccess(this.pc2);
},
this.onSetSessionDescriptionError.bind(this)
);
this.trace('pc1 setRemoteDescription start');
this.pc1.setRemoteDescription(desc).then(
() => {
this.onSetRemoteSuccess(this.pc1);
},
this.onSetSessionDescriptionError.bind(this)
);
}
onIceCandidate(pc, event) {
this.getOtherPc(pc).addIceCandidate(event.candidate)
.then(
() => {
this.onAddIceCandidateSuccess(pc);
},
(err) => {
this.onAddIceCandidateError(pc, err);
}
);
this.trace(this.getName(pc) + ' ICE candidate: \n' + (event.candidate ?
event.candidate.candidate : '(null)'));
}
onAddIceCandidateSuccess(pc) {
this.trace(this.getName(pc) + ' addIceCandidate success');
}
onAddIceCandidateError(pc, error) {
this.trace(this.getName(pc) + ' failed to add ICE Candidate: ' + error.toString());
}
onIceStateChange(pc, event) {
if (pc) {
this.trace(this.getName(pc) + ' ICE state: ' + pc.iceConnectionState);
console.log('ICE state change event: ', event);
}
}
hangup() {
this.trace('Ending call');
this.pc1.close();
this.pc2.close();
this.pc1 = null;
this.pc2 = null;
this.hangupButtonDisabled = true;
this.callButtonDisabled = false;
}
trace(arg) {
var now = (window.performance.now() / 1000).toFixed(3);
console.log(now + ': ', arg);
}
}
component.scss
.videoCall{
display: flex;
.self{
width: 264px;
height: 298px;
border: 2px solid black;
}
.partner{
margin-left: 10px;
width: 264px;
height: 298px;
border: 2px solid black;
}
}
I am unable to call between two users. how will I do that. Or is there any other way to integrate video call in angualr 8 please suggest me. Please help me out
Thanks

Apply CSS Filters to cropped image and save/upload

I can already, input the image and crop it. I tried to apply CSS filters to it, but seems the CSS filters only apply on the img tag, not the actual image.
I am using both #Alyle-cropping and ngx-image-cropper(tests). Both give to me a base64 string for the cropped image. I am able to load the cropped image to the img tag and also upload it to the database.
onCropped(e: ImgCropperEvent) {
this.croppedImage = e.dataURL;
// console.log('cropped img: ', e.dataURL);
}
onloaded(e: ImgCropperEvent) {
this.imagemOriginal = e.originalDataURL;
this.cropper.center();
console.log('img loaded', e.name);
}
onerror(e: ImgCropperErrorEvent) {
console.warn(`'${e.name}' is not a valid image`, e);
}
// Aplicar Filtros /////////////////////////////////////////////////
change(crop: Crop): void {
this.stylus = crop.nome;
this.crops.forEach(function (value) {
(value.nome === crop.nome) ? value.ehSelec = true : value.ehSelec = false;
});
// const canvas = document.getElementById('cropping'), image = document.createElement('img');
// image.src = canvas.toDataURL('image/jpeg', 1.0);
// document.body.appendChild(image);
}
enviarParanue(): void {
const ref = firebase.storage().ref(`imagens/usuarios/idTeste`).child(`nomeTeste`);
const stringa = this.removerString(this.croppedImage);
ref.put(this.base64toBlob(stringa, 'image/png')).then((snapshot) => {
// console.log('snapshot', snapshot.valueOf());
ref.getDownloadURL().then(function(downloadURL) {
console.log('File available at', downloadURL);
});
});
// ref.putString(stringa, 'base64', {contentType: 'image/png'}).then((snapshot) => {
// // console.log('snapshot', snapshot.valueOf());
// ref.getDownloadURL().then(function(downloadURL) {
// console.log('File available at', downloadURL);
// });
// });
}
removerString(stringa: string): string {
return stringa.substring(23);
}
base64toBlob(base64Data: any, contentType: any) {
contentType = contentType || '';
const sliceSize = 1024;
const byteCharacters = atob(base64Data);
const bytesLength = byteCharacters.length;
const slicesCount = Math.ceil(bytesLength / sliceSize);
const byteArrays = new Array(slicesCount);
for (let sliceIndex = 0; sliceIndex < slicesCount; ++ sliceIndex) {
const begin = sliceIndex * sliceSize;
const end = Math.min(begin + sliceSize, bytesLength);
const bytes = new Array(end - begin);
for (let offset = begin, i = 0 ; offset < end; ++i, ++offset) {
bytes[i] = byteCharacters[offset].charCodeAt(0);
}
byteArrays[sliceIndex] = new Uint8Array(bytes);
}
return new Blob(byteArrays, { type: contentType });
}
EXAMPLE OF THE CSS FILTERS:
.none {filter:none;}
.blur {filter:blur(2.5px);}
.brightness {filter:brightness(200%);}
.contrast {filter:contrast(200%);}
.drop-shadow {filter:drop-shadow(8px 8px 10px gray);}
.grayscale {filter:grayscale(100%);}
.hue-rotate {filter:hue-rotate(90deg);}
.invert {filter:invert(100%);}
.opacity {filter:opacity(30%);}
.saturate {filter:saturate(8);}
.sepia {filter:sepia(100%);}
.contrast-brightness {filter:contrast(200%) brightness(150%);}
Problem is... I don't know how to apply the CSS filters to the image to upload the cropped with the effects(sepia, contrast, etc).
I tried to get the img src and convert it to Blob, but didn't work.
I ended up saving in the database a string with the name of the filter. So I apply the filter when I load the image. A good side of it, is that I can change the filter whenever I want.

How to change CSS of columns - ReactTable

I am using react-table in my application.
I am stuck in doing one thing i.e. changing the CSS of columns while a column is being resized.
Currently when you resize a column only cursor changes. What I want is to add border to the selected column.
I searched for this on SO and google as well. But couldn't find anything useful. And In the documentation as well nothing is mentioned about this topic as well.
Update
Now I am able to add border while dragging the column while resizing. I am able to do so by adding and removing the class.
What I did to do so:
Created a var in the state for className:
this.state = {
addBorder: null
}
Passed this class name in my column:
const columns = [{
Header: 'Name',
accessor: 'name', // String-based value accessors!,
headerClassName: this.state.addBorder,
className: this.state.addBorder
}, {
Header: 'Age',
accessor: 'age',
Cell: props => <span className='number'>{2}</span> // Custom cell components!
}, {
id: 'friendName', // Required because our accessor is not a string
Header: 'Friend Name',
accessor: d => d.friend.name // Custom value accessors!
}, {
Header: props => <span>Friend Age</span>, // Custom header components!
accessor: 'friend.age'
}];
return (
<div onMouseUp={this.handleMouseUp}>
<ReactTable
data={data}
columns={columns}
resizable={true}
onResizedChange={(col, e) => {
const column = col[col.length-1];
this.setState({addBorder: column.id})
}} />
</div>
)
}
To remove the class when dragging ends:
handleMouseUp (e) {
this.setState({addBorder: null});
}
But I am still not able to add border on hover.
Now, I am sending my custom HTML in header props. And in my HTML I have made an extra div. And I have moved this div to right. And on hover of this div, I am emitting mouse events and changing CSS accordingly.
But Existing div in the header that is responsible for resizing column is overlapping with my Div.
Header: props => <div className='header-div'> Name <div onMouseOver = {() => {
console.log('mose');
this.setState({className: 'addBorder'});
}} className='hover-div' onMouseOut = {() => {console.log('sdasd');this.setState({className: null});}}> </div></div> ,
From what I understand, you want to add some border when you hover over a column header. If my understanding is correct, you can use :hover pseudo selector over the header class
.hdrCls:hover {
border: 2px solid rgba(0,0,0,0.6) !important;
}
Update :
You can manipulate state in onResizedChange handler exposed by react-table
onResizedChange={(newResized, event) => {
let resizedCol = newResized.slice(-1)[0].id;
if(this.state.activeCol !== resizedCol) {
this.setState({
activeCol: resizedCol,
resizing: true
})
}
}}
Also, make sure you have to make the resizing state to false on mouseup event. For that I have come up with the below solution.
componentDidUpdate(props, state) {
if (this.state.resizing && !state.resizing) {
document.addEventListener('mouseup', this.onMouseUp);
} else if (!this.state.resizing && state.resizing) {
document.removeEventListener('mouseup', this.onMouseUp);
}
}
onMouseUp = (evt) => {
this.setState({
activeCol: '',
resizing: false
});
evt.stopPropagation();
evt.preventDefault();
}
For reference:
const ReactTable = window.ReactTable.default
class App extends React.Component {
constructor(props) {
super(props)
this.state = {
activeCol: '',
resizing: false
}
}
componentDidUpdate(props, state) {
if (this.state.resizing && !state.resizing) {
document.addEventListener('mouseup', this.onMouseUp);
} else if (!this.state.resizing && state.resizing) {
document.removeEventListener('mouseup', this.onMouseUp);
}
}
onMouseUp = (evt) => {
this.setState({
activeCol: '',
resizing: false
});
evt.stopPropagation();
evt.preventDefault();
}
render() {
const data = [{
name:"Mark",
age:24
},
{
name:"Derek",
age:26
}]
const columns = [{
Header: 'Name',
accessor: 'name', // String-based value accessors!,
headerClassName: 'hdrCls',
className: (this.state.activeCol === 'name') && this.state.resizing ? 'borderCellCls' : 'defaultCellCls'
}, {
Header: 'Age',
accessor: 'age',
headerClassName: 'hdrCls',
className: (this.state.activeCol === 'age') && this.state.resizing ? 'borderCellCls' : 'defaultCellCls'
}];
return <ReactTable
data = { data }
columns = { columns }
showPagination= {false}
onResizedChange={(newResized, event) => {
let resizedCol = newResized.slice(-1)[0].id;
if(this.state.activeCol !== resizedCol) {
this.setState({
activeCol: resizedCol,
resizing: true
})
}
}}
/>
}
}
ReactDOM.render( < App / > , document.getElementById("app"))
.hdrCls:hover {
border: 2px solid rgba(0,0,0,0.6) !important;
}
.borderCellCls {
border-right: 2px solid rgba(0,0,0,0.6) !important;
border-left: 2px solid rgba(0,0,0,0.6) !important;
}
.defaultCellCls {
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-table/6.7.6/react-table.js"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/react-table/6.7.6/react-table.css"></link>
<div id="app"></div>
You can play around with CSS. Hope this is what you want and hope this helps.
Update:
I think you have to play with CSS to achieve what you desire.
.borderCellCls {
border-right: 2px solid rgba(0,0,0,0.6) !important;
border-left: 2px solid rgba(0,0,0,0.6) !important;
}
If you are here to find out how to set className to a column cell (with the react-table), here is the solution:
1)
<tr
{...row.getRowProps()}
>
{row.cells.map((cell) => (
<td
{...cell.getCellProps([
{
className: cell.column.className, // pay attention to this
style: cell.column.style,
// set here your other custom props
},
])}
>
{cell.render('Cell')}
</td>
))}
</tr>
2)
const columns = React.useMemo(
() => [
{
Header: 'Date',
accessor: 'date',
minWidth: 70,
className: 'text-dark fw-bolder fs-6 min-w-70px', // pass className props here
headerClassName: 'text-muted', // or another props like this one
}]
<Table columns={columns} ... />
And finally, those props will be passed to your cells
For TypeScript support follow the instructions in DefinitelyTyped, ie. create the file /src/types/react-table-config.d.ts with the content from the instructions, then add the following to it to support custom properties on your column (add more properties in the last line as required):
// Added to support classes to template from:
// https://github.com/DefinitelyTyped/DefinitelyTyped/tree/master/types/react-table
export interface ColumnInstance<
D extends Record<string, unknown> = Record<string, unknown>
> extends Omit<ColumnInterface<D>, 'id'>,
ColumnInterfaceBasedOnValue<D>,
UseTableColumnProps<D>,
Record<headerClassName | className, string> {}

Scrolling only side menu div and others should be fixed when menu is open

I have a side menu and when it's open, the body can be partially seen. My side menu might be long so you could scroll on it. But when the menu is at the bottom you then scroll on the body, and I don't want this behaviour.
Similar to Scrolling only content div, others should be fixed but I'm using React. Other content should be scrollable when my side menu is closed. Think of the content as side menu in the example in the link. So far I'm using the same technique provided by that answer but it's ugly (kinda jQuery):
preventOverflow = (menuOpen) => { // this is called when side menu is toggled
const body = document.getElementsByTagName('body')[0]; // this should be fixed when side menu is open
if (menuOpen) {
body.className += ' overflow-hidden';
} else {
body.className = body.className.replace(' overflow-hidden', '');
}
}
// css
.overflow-hidden {
overflow-y: hidden;
}
What should I do with Reactjs?
You should make a meta component in react to change things on the body as well as changing things like document title and things like that. I made one a while ago to do that for me. I'll add it here.
Usage
render() {
return (
<div>
<DocumentMeta bodyClasses={[isMenuOpen ? 'no-scroll' : '']} />
... rest of your normal code
</div>
)
}
DocumentMeta.jsx
import React from 'react';
import _ from 'lodash';
import withSideEffect from 'react-side-effect';
var HEADER_ATTRIBUTE = "data-react-header";
var TAG_NAMES = {
META: "meta",
LINK: "link",
};
var TAG_PROPERTIES = {
NAME: "name",
CHARSET: "charset",
HTTPEQUIV: "http-equiv",
REL: "rel",
HREF: "href",
PROPERTY: "property",
CONTENT: "content"
};
var getInnermostProperty = (propsList, property) => {
return _.result(_.find(propsList.reverse(), property), property);
};
var getTitleFromPropsList = (propsList) => {
var innermostTitle = getInnermostProperty(propsList, "title");
var innermostTemplate = getInnermostProperty(propsList, "titleTemplate");
if (innermostTemplate && innermostTitle) {
return innermostTemplate.replace(/\%s/g, innermostTitle);
}
return innermostTitle || "";
};
var getBodyIdFromPropsList = (propsList) => {
var bodyId = getInnermostProperty(propsList, "bodyId");
return bodyId;
};
var getBodyClassesFromPropsList = (propsList) => {
return propsList
.filter(props => props.bodyClasses && Array.isArray(props.bodyClasses))
.map(props => props.bodyClasses)
.reduce((classes, list) => classes.concat(list), []);
};
var getTagsFromPropsList = (tagName, uniqueTagIds, propsList) => {
// Calculate list of tags, giving priority innermost component (end of the propslist)
var approvedSeenTags = {};
var validTags = _.keys(TAG_PROPERTIES).map(key => TAG_PROPERTIES[key]);
var tagList = propsList
.filter(props => props[tagName] !== undefined)
.map(props => props[tagName])
.reverse()
.reduce((approvedTags, instanceTags) => {
var instanceSeenTags = {};
instanceTags.filter(tag => {
for(var attributeKey in tag) {
var value = tag[attributeKey].toLowerCase();
var attributeKey = attributeKey.toLowerCase();
if (validTags.indexOf(attributeKey) == -1) {
return false;
}
if (!approvedSeenTags[attributeKey]) {
approvedSeenTags[attributeKey] = [];
}
if (!instanceSeenTags[attributeKey]) {
instanceSeenTags[attributeKey] = [];
}
if (!_.has(approvedSeenTags[attributeKey], value)) {
instanceSeenTags[attributeKey].push(value);
return true;
}
return false;
}
})
.reverse()
.forEach(tag => approvedTags.push(tag));
// Update seen tags with tags from this instance
_.keys(instanceSeenTags).forEach((attr) => {
approvedSeenTags[attr] = _.union(approvedSeenTags[attr], instanceSeenTags[attr])
});
instanceSeenTags = {};
return approvedTags;
}, []);
return tagList;
};
var updateTitle = title => {
document.title = title || document.title;
};
var updateBodyId = (id) => {
document.body.setAttribute("id", id);
};
var updateBodyClasses = classes => {
document.body.className = "";
classes.forEach(cl => {
if(!cl || cl == "") return;
document.body.classList.add(cl);
});
};
var updateTags = (type, tags) => {
var headElement = document.head || document.querySelector("head");
var existingTags = headElement.querySelectorAll(`${type}[${HEADER_ATTRIBUTE}]`);
existingTags = Array.prototype.slice.call(existingTags);
// Remove any duplicate tags
existingTags.forEach(tag => tag.parentNode.removeChild(tag));
if (tags && tags.length) {
tags.forEach(tag => {
var newElement = document.createElement(type);
for (var attribute in tag) {
if (tag.hasOwnProperty(attribute)) {
newElement.setAttribute(attribute, tag[attribute]);
}
}
newElement.setAttribute(HEADER_ATTRIBUTE, "true");
headElement.insertBefore(newElement, headElement.firstChild);
});
}
};
var generateTagsAsString = (type, tags) => {
var html = tags.map(tag => {
var attributeHtml = Object.keys(tag)
.map((attribute) => {
const encodedValue = HTMLEntities.encode(tag[attribute], {
useNamedReferences: true
});
return `${attribute}="${encodedValue}"`;
})
.join(" ");
return `<${type} ${attributeHtml} ${HEADER_ATTRIBUTE}="true" />`;
});
return html.join("\n");
};
var reducePropsToState = (propsList) => ({
title: getTitleFromPropsList(propsList),
metaTags: getTagsFromPropsList(TAG_NAMES.META, [TAG_PROPERTIES.NAME, TAG_PROPERTIES.CHARSET, TAG_PROPERTIES.HTTPEQUIV, TAG_PROPERTIES.CONTENT], propsList),
linkTags: getTagsFromPropsList(TAG_NAMES.LINK, [TAG_PROPERTIES.REL, TAG_PROPERTIES.HREF], propsList),
bodyId: getBodyIdFromPropsList(propsList),
bodyClasses: getBodyClassesFromPropsList(propsList),
});
var handleClientStateChange = ({title, metaTags, linkTags, bodyId, bodyClasses}) => {
updateTitle(title);
updateTags(TAG_NAMES.LINK, linkTags);
updateTags(TAG_NAMES.META, metaTags);
updateBodyId(bodyId);
updateBodyClasses(bodyClasses)
};
var mapStateOnServer = ({title, metaTags, linkTags}) => ({
title: HTMLEntities.encode(title),
meta: generateTagsAsString(TAG_NAMES.META, metaTags),
link: generateTagsAsString(TAG_NAMES.LINK, linkTags)
});
var DocumentMeta = React.createClass({
propTypes: {
title: React.PropTypes.string,
titleTemplate: React.PropTypes.string,
meta: React.PropTypes.arrayOf(React.PropTypes.object),
link: React.PropTypes.arrayOf(React.PropTypes.object),
children: React.PropTypes.oneOfType([
React.PropTypes.object,
React.PropTypes.array
]),
bodyClasses: React.PropTypes.array,
},
render() {
if (Object.is(React.Children.count(this.props.children), 1)) {
return React.Children.only(this.props.children);
} else if (React.Children.count(this.props.children) > 1) {
return (
<span>
{this.props.children}
</span>
);
}
return null;
},
});
DocumentMeta = withSideEffect(reducePropsToState, handleClientStateChange, mapStateOnServer)(DocumentMeta);
module.exports = DocumentMeta;
This component could probably be changed a little for your case (withSideEffect is used for both client and server side rendering... if you arent using server side rendering then its probably not completely necessary) but the component will work on client side rendering if you would like to use it there as well.
ReactJS doesn't have direct access to the <body> element, and that's the element that needs to have its overflow-y style changed. So while what you're doing isn't perhaps the prettiest code, it's not entirely wrong either.
The only real suggestion I'd give is (shudder) using inline styles on the body instead of a classname so as to avoid having to introduce the CSS declaration. As long as your menu is the only thing responsible for updating the overflow-y attribute, there's no reason you can't use an inline style on it. Mashing that down with the ?: operator results in fairly simple code:
body.style.overflowY = menuOpen ? "hidden" : "";
And then you can just delete the .overflow-hidden class in its entirety.
If for some reason multiple things are managing the overflow state of the body, you might want to stick with classnames and assign a unique one for each thing managing it, something like this:
if (menuOpen) {
body.className += ' menu-open';
}
else {
// Use some tricks from jQuery to remove the "menu-open" class more elegantly.
var className = " " + body.className + " ";
className = className.replace(" overflow-hidden ", " ").replace(/\s+/, " ");
className = className.substr(1, className.length - 2);
}
CSS:
body.menu-open {
overflow-y: hidden;
}

Resources