ClearButton in react-bootstrap-typeahead with 2 X superimposed - react-bootstrap-typeahead

I have a graphical bug with the ClearButton provided by react-bootstrap-typeahead. I wanted to replace it with another button (for instance the CloseButton from Bootstrap). Unfortunately, the CloseButton is not clickable.
Graphical bug
What do I do wrong ?
import { CloseButton } from "react-bootstrap";
import { Typeahead, ClearButton } from "react-bootstrap-typeahead";
import "bootstrap/dist/css/bootstrap.min.css";
import "react-bootstrap-typeahead/css/Typeahead.css";
<Typeahead
id="search"
options={chartData.datasets}
labelKey={chartData.datasets.label}
placeholder="look for projects"
multiple
onChange={setMultiSelections}
selected={multiSelections}
>
{({ onClear, selected }) => (
<div className="rbt-aux">
{!!selected.length && <CloseButton onClick={onClear} />}
</div>
)}
</Typeahead>
try to change rbt-close-content css class
I used react-bootstrap-typeahead/css/Typeahead.bs5.css instead.

It seems like there may be two different issues here:
1. The clear button isn't clickable.
This is happening because .rbt-aux has pointer-events: none; set to avoid blocking clicks on the input. The default close button then has pointer-events: auto; to enable clicks on the button. You'll need to add something similar, or define your own element to position the clear button that doesn't remove pointer-events.
.rbt-aux .btn-close {
pointer-events: auto;
}
2. There are two instances of the clear button being rendered in the component.
I'm not sure why this is happening. Based on your code sample, there should only be one button rendering. If you plan on rendering a custom clear button, be sure not to set the clearButton prop on the typeahead (or set it to false, which is the default).

Related

Modal for fullsize image with gatsby-image - limit height and width

What I want to achive
I am using gatsby and want to design an image gallery. Clicking on one of the images shall open a modal, which: (1) is showing the image in maximum possible size, so that it still fits into the screen and (2) is centered in the screen.
My Code
/* imagemodal.js */
import React from 'react'
import * as ImagemodalStyles from './imagemodal.module.css'
import { Modal } from 'react-bootstrap'
import Img from 'gatsby-image'
import { useStaticQuery, graphql } from 'gatsby'
export default function Imagemodal() {
const data = useStaticQuery(graphql`
query {
file(relativePath: { eq: "images/mytestimage.jpg" }) {
childImageSharp {
fluid(maxWidth: 1200) {
...GatsbyImageSharpFluid
}
}
}
}
`)
return (
<div>
<Modal
show={true}
centered
className={ImagemodalStyles.imageModal}
dialogClassName={ImagemodalStyles.imageModalDialog}
onHide={(e) => console.log(e)}
>
<Modal.Header closeButton />
<Modal.Body className={ImagemodalStyles.imageModalBody}>
<h1>TestInhalt</h1>
<Img fluid={data.file.childImageSharp.fluid} />
</Modal.Body>
</Modal>
</div>
)
}
/* imagemodal.module.scss */
.imageModalDialog {
display: inline-block;
width: auto;
}
.imageModal {
text-align: center;
}
.imageModalBody img {
max-height: calc(100vh - 225px);
}
The Problem
The image does not scale to the screen size. The image is either too big - so it flows over the vieport - or it is too small. Secondly, the modal size does not respond to the image size correctly and / or is not centered.
What I tried
I used this suggestion for the CSS: How to limit the height of the modal?
I tried as well dozens of other CSS parameter combinations. But I could not find a working solution.
I tried to format the gatsby-image directly with a style-tag.
I tried as well react-modal but had similar problems.
Does anyone have a good solution to show a gatsby-image in full screen size in a responsive modal? For me it is okay to use either the bootstrap-modal or react-modal - or any other suitable solution.
Edit
In the end I ended up with a workaround. I used react-image-lightbox and took the Image-Source from gatsby-image as the input for lightbox. My component gets the data from the graphQL query in the props via props.imageData.
This works quite well for me:
import Lightbox from 'react-image-lightbox';
...
export default function Imagegallery(props) {
...
const allImages = props.imageData.edges
const [indexImageToShow, setIndexImageToShow] = useState()
...
return(
<Lightbox
mainSrc={allImages[indexImageToShow].node.childrenImageSharp[0].fluid.src}
...
/>
Special thanks to #FerranBuireu to point me to the right direction
Assuming that the functionality works as expected, as it seems, it's a matter of CSS rules, not React/Gatsby issue. The following rule:
.imageModalBody img {
max-height: calc(100vh - 225px);
}
It Will never be applied properly, since gatsby-image creates an output of HTML structure of nested <div>, <picture> and <img> so your rule will be affected by the inherited and relativity of the HTML structure. In other words, you are not pointing to the image itself with that rule because of the result HTML structure.
You should point to the <Img>, which indeed, it's a wrapper, not an <img>.
return (
<div>
<Modal show={true} onHide={handleClose} centered className={ImagemodalStyles.imageModal} dialogClassName={ImagemodalStyles.imageModalDialog}>
<Modal.Header closeButton />
<Modal.Body>
<Img className={ImagemodalStyles.imageModalBody} fluid={props.data.file.childImageSharp.fluid} />
</Modal.Body>
</Modal>
</div>
)
The snippet above will add the (spot the difference, without img):
.imageModalBody {
max-height: calc(100vh - 225px);
}
To the wrapper, which may or may not fix the issue, but at least will apply the rule correctly. It's difficult to know what's wrong without a CodeSandbox but you will apply the styles correctly with this workaround.
Keep always in mind that when using gatsby-image, the <img> it's profound in the resultant HTML structure so your styles should apply to the outer wrapper of it.

View is not resized onKeyboardWillShow, but onKeyboardDidShow

When I click the keyboard and it opens op, the keyboard first finishes the animation. Then the view is resized. This causes for lag when the view scrolls to the field.
I have tried fixing it this way:
this.keyboard.onKeyboardWillShow().then(() => {
this.content.scrollToBottom();
});
But because the view is not yet resized, it does not scroll. How can I fix this? The app doesn't feel native at all without this.
The solution I found for this is as follows.
I created a wrapper within my <ion-content>:
<ion-content>
<div id="wrapper"></div>
</ion-content>
And styled it this way:
#wrapper{
height: 100%;
margin-bottom: 200px; /* it is best to get the keyboard height here from `onKeyboardWillShow()` */
}
Then in ts I did this:
this.keyboard.setResizeMode(KeyboardResizeMode.None);
this.keyboard.onKeyboardWillShow().subscribe(() => {
this.content.scrollToBottom();
});
this.keyboard.onKeyboardWillHide().subscribe(() => {
this.content.scrollToTop(500);
});
Works just like I want it. But I am still open to better answers.

How to change expansion panel icon position to the left?

In my app, the expansion arrow has to be in the left side of the panel.
But, by default it's displaying in the right side.
This :
<ExpansionPanelSummary
className={classes.panelSummary}
expandIcon={<ExpandMoreIcon />}
IconButtonProps={{edge: 'start'}}
aria-controls='panel1a-content'
id='panel1a-header'
>
Doesn't made it.
Granted, you can't (easily) change the order in which the components appear in the HTML. However, there is a way using only CSS. ExpansionPanelSummary uses display: flex; you can therefore set the order property on the icon to make it appear to the left of the content.
This can be achieved with either useStyles or withStyles (Or possibly using plain CSS, but I haven't tried it); here's how you'd go about using the latter:
import withStyles from "#material-ui/core/styles/withStyles";
const IconLeftExpansionPanelSummary = withStyles({
expandIcon: {
order: -1
}
})(ExpansionPanelSummary);
You can then write the rest of your code using IconLeftExpansionPanelSummary instead of ExpansionPanelSummary when you want the icon to appear to the left. Don't forget to set IconButtonProps={{edge: 'start'}} on the component for proper spacing.
<AccordionSummary
className={classes.accordionSummary}
classes={{
expandIcon: classes.expandIcon,
expanded: classes.expanded
}}
IconButtonProps={{
disableRipple: true
}}
></AccordionSummary>
You can add class and use flex-direction
accordionSummary: {
flexDirection: 'row-reverse'
}
It's simple
add class on <ExpansionPanelSummary> like this
<ExpansionPanelSummary className={classes.panelSummary}>
add css against this class in jss like this
panelSummary:{flexDirection: "row-reverse"},
In case using css
add class on <ExpansionPanelSummary> like this
<ExpansionPanelSummary className="panelSummary">
add css against this class in jss like this
.panelSummary{flex-direction: row-reverse;}
you can get the expansion panel icon on left by removing it from expandIcon and add it as a children in Summary something like this
<ExpansionPanel defaultExpanded={true}>
<ExpansionPanelSummary aria-controls="panel1a-content">
{this.state.expanded ? <RemoveIcon/> : <ExpandIcon />}
<Typography component='h4' variant='h4'>My Expansion Panel</Typography>
</ExpansionPanelSummary>
<ExpansionPanelsDetails />
</ExpansionPanel>
The challenge is that the order is hardcoded into the codebase and you will not be able to use the ExpansionPanel as is.
If you look at the implementation, you will find the code as below
<div className={clsx(classes.content, { [classes.expanded]: expanded })}>{children}</div>
{expandIcon && (
<IconButton
disabled={disabled}
className={clsx(classes.expandIcon, {
[classes.expanded]: expanded,
})}
edge="end"
component="div"
tabIndex={-1}
aria-hidden
{...IconButtonProps}
>
{expandIcon}
</IconButton>
)}
As you see the <div> contains the text and then the IconButton is displayed.
So, you may have to work with what's provided out of the box or create your own Component based on what material-UI provides.
Hope that helps.
You can modify the CSS class like this:
notice the absolute position, in this way you can move the div that contains the icon whatever position you want with 'left' or 'right' properties
const useStyles = makeStyles((theme) => ({
ExpansionPanelSummaryExpandedIcon: {
'& div.MuiExpansionPanelSummary-expandIcon': {
position: 'absolute',
right: '5%',
},
}
}));
and then use in the ExpansionPanelSummary
<ExpansionPanelSummary
expandIcon={<ExpandMoreIcon />}
aria-controls="panel1-content"
id="panel1bh-header"
className={classes.ExpansionPanelSummaryExpandedIcon}
>
references:
https://cssinjs.org/?v=v10.3.0
https://v4-8-3.material-ui.com/customization/components/#overriding-styles-with-classes

Angular 2: change DOM elements outside the root AppComponent

I have an observable -- loading$ -- that outputs true when I want to show a loading overlay in the UI, and false when it's time to remove that overlay. The visibility is controlled with a CSS class.
When the Observable emits true, I want to add the CSS class to the <body>, and remove it on false.
html
<body class="">
<my-app>Angular app goes here</my-app>
</body>
As you can see, the <body> is outside of my Angular 2 app, so I cannot use property binding to change the class. This is what I currently do:
AppComponent.ts
loading$.subscribe(loading =>{
if(loading) document.querySelector('body').classList.add('loading');
else document.querySelector('body').classList.remove('loading');
});
This works well. The loading class is added/removed when the loading$ observable emits true/false. The problem is that I'd like to run my app in a web worker which means no access to the DOM. Plus, Angular recommends against manipulating the DOM directly.
How can I use Angular APIs to change <body>?
Angular 2, typescript 2, rxjs 5 beta 12
PS: This question looks promising, but the key link is dead. I've also seen a couple of suggestions that worked with Beta releases but are now obsolete (example)
If you want an uninterrupted animation by DOM replacement in the middle of bootstrapping the app then use the following approach.
Put the overlay after my-app element.
<my-app></my-app>
<div id="overlay"></div>
Add #HostBinding to class.ready in main component app.component.ts:
#HostBinding('class.ready') ready: boolean = false;
Change the property when the data is loaded with the initial call (splash is still visible when your screen is not fully rendered).
constructor(private contextService: ContextService) {
someService.initCall().then(r => this.ready = true);
}
Use CSS to hide the loader:
my-app.ready + .overlay {
animation: hideOverlay 1s forwards;
}
#keyframes hideOverlay {
0% {
opacity: 1;
}
100% {
opacity: 0;
visibility: hidden;
}
}
#ktretyak 's solution got things to work for me. Basically, instead of using <body> I put the div that takes the loading class within <my-app>
index.html
<body>
<my-app>
<div id="overlay" class="loading"></div>
</my-app>
</body>
CSS
#overlay {
position:fixed;
top:0;
left:0;
bottom:0;
right:0;
display:none;
}
#overlay.loading {
display:block
}
Thanks to the CSS styles, and the fact that loading is enabled to start, the overlay is shown while the Javascript loads and Angular bootstraps.
Once Angular is loaded though, everything within <my-app> will be replaced with AppComponent template. As I explained in the comments beneath the OP, I need ongoing access to this loading overlay, because I show it during the loading of new routes (as users navigate from one routed component to another). So I had to include the same overlay div in the template
app.component.html
<div id="overlay" [class.loading]="showLoading"></div>
<router-outlet></router-outlet>
Now, when my loading$ observable fires, I change a class property showLoading and Angular takes care of updating the overlay since it is now part of AppComponent
app.component.ts
// set to true to show loading overlay. False to hide
showLoading:boolean = true;
ngOnInit(){
// show/hide overlay depending on value emitted
loading$.subscribe(loading => this.showLoading = loading);
}
Try to do like this:
<my-app>
<div class="your-class-for-loading"></div>
</my-app>
When my-app is ready, div will be automatically removed.

Add button to fancytree node to delete/remove that node?

I cannot find an example to do this anywhere, although I could have sworn I've seen one in the past.
I want to add a button to a node in fancytree so that either on hovering over that node (or maybe on selecting it) the button displays (a white x on a red circle, for example) and clicking it will delete/remove that node. At all other times the delete button should be hidden for the node.
I've been unable to find any kind of example where a custom link or button is added to a fancytree node though - maybe it's not possible to do or I'm just using the wrong search terms?
Edit: I found a way to add a clickable button by appending html to the title string:
title: component.name() + "<span class='deleteButton'><a href='#' data-bind='click: myfunction'><img src='../../Content/images/deleteIcon.png' /></a></span>",
And by adding some custom css to my site file:
span.fancytree-node span.deleteButton {
display: none;
}
span.fancytree-active span.deleteButton {
margin-left: 10px;
display: inline-block;
}
But this adds the button to the title text and is therefore subject to the highlighting of the title when active. It would be better if there was a way to add this to the node OUTSIDE of the title text. Is that possible Martin?
$("#tree").fancytree({
source: [...],
renderNode: function (event, data) {
var node = data.node;
var $nodeSpan = $(node.span);
// check if span of node already rendered
if (!$nodeSpan.data('rendered')) {
var deleteButton = $('<button type="button" class="btn">delete node</button>');
$nodeSpan.append(deleteButton);
deleteButton.hide();
$nodeSpan.hover(function () {
// mouse over
deleteButton.show();
}, function () {
// mouse out
deleteButton.hide();
})
// span rendered
$nodeSpan.data('rendered', true);
}
}
});
I normally use css ':after' for such cases (https://developer.mozilla.org/de/docs/Web/CSS/::after).
If that is not enough, you can always tweak the markup in the 'renderNode' event.