I am building a project based on this React Template.
In one of the componenets I have a Select List and under it there's a Card element.
The problem is that when I click on the list the items appear under the card element as you see below:
I had a feeling this was caused by the CSS code of the template itself that configures the card to appear over all other elements.
So what I did is I created a new react project with:
npx create-react-app
And my suspicion was right.
I copied basically the same code:
const selectStyles = {
control: (styles) => ({ ...styles, backgroundColor: "white" }),
option: (styles) => {
return {
...styles,
backgroundColor: "green",
"z-index": -5,
};
},
};
export default class App extends Component {
render() {
return (
<Fragment>
<Select
className="basic-single"
classNamePrefix="select"
defaultValue={colourOptions[0]}
name="color"
options={colourOptions}
styles={selectStyles}
/>
<Card
style={{
position: "absolute",
"background-color": "red",
"z-index": 5,
}}
>
<CardImg
top
width="100%"
src="/assets/318x180.svg"
alt="Card image cap"
/>
<CardBody>
<CardTitle tag="h5">Card title</CardTitle>
<CardSubtitle tag="h6" className="mb-2 text-muted">
Card subtitle
</CardSubtitle>
<CardText>
Some quick example text to build on the card title and make up the
bulk of the card's content.
</CardText>
<Button>Button</Button>
</CardBody>
</Card>
</Fragment>
);
}
}
And the select items appear ABOVE the card:
The card is colored in red.
CONCLUSION: The problem is caused by the card css code of the template.
As you see, I tried with different configurations with the z-index attribute, but to no avail.
Any idea how to fix this?
The problem is with the z-index and position, whichever content you want to show in the top should have higher z-index value.
Try giving the select dropdown the high values compared to card.
Try removing both css attributes position: absolute and z-index if it is not needed. Position absolute is only used when to need to move the content to wherever you want to the respective relative parent container. So if you are just practicing and not doing design try to remove both.
Related
original title: VueJS with Bootstrap, stretched link with hidden button
I am trying to make clickable bootstrap cards in a VueJS project, I want clicking the card to open a collapsible element within the card, right now I have something that works using a button with the "stretched-link" class
<b-card
v-bind:img-src="`https://via.placeholder.com/200`"
img-alt="Image"
img-top
tag="article"
style="max-width: 20rem;"
class="mb-2">
<b-button v-b-toggle="'collapse-' + unique-identifier" variant="primary" class="stretched-link ">Toggle</b-button>
<b-collapse v-bind:id="'collapse-' + unique-identifier" class="mt-2">
<b-card>This is the collapsed content</b-card>
</b-collapse>
</b-card>
I'm trying to make this work without having a visible button in the card, I've tried using the invisibile class and the d-none class (class="stretched-link d-none" and class="stretched-link invisible")
In both cases, the button is made invisible but so is the stretched link. Is there any way to maintain the active stretched link area while hiding the button icon?
ok I figured out a solution for my particular use case with JQuery,
What I was really after was a clickable image with the functionality of a bootstrap button. So this is a solution for that problem, not exactly hidden stretched links.
In my Vue component I define the triggerButton method, which finds a button by its id and clicks it.
<script>
import $ from 'jquery'
export default {
props: //just filling this for structure,
data() {
return {
//stuff
}
},
async mounted() {
//more placeholder structure
},
methods: {
//here is the sauce
triggerButton(id) {
$("#"+id).click();
},
}
}
</script>
Then in the <template> body I have a hidden button. "unique-identifier" is just a unique number that is put in, it's needed in my case because im generating many of these elements in a for loop and they need to have distinct ids.
In this case I'm using the bootstrap button to trigger a unique modal.
<b-button style="display:none" v-bind:id="'button'+ unique-identifier" v-b-modal="'modal' + unique-identifier">Launch centered modal</b-button>
Then finally I have an image displayed in the bootstrap card, and on click i call the triggerButton method that will now display my modal.
<img v-on:click="showModal('button' + unique-identifier)" :src="`https://placekitten.com/g/200/200`"/>
Here is a code sandbox example where I am trying to display a map inside of a specific component. But, I am not able to get the deck.gl and react-map-gl divs to live inside their parent. instead, they spill out to the extent of the document body.
The basic layout of the example is:
<Box id='mapcontainer'>
<DeckGL id="deck-gl">
<MapView id="map" >
<StaticMap/>
</MapView>
</DeckGL>
</Box>
It appears that Deck.gl is creating a <div> and a <canvas> element between the <Box id='mapcontainer'> div and the <DeckGL id='deck-gl'> div, and I can not get the div and canvas to live inside of their parent Box.
The id of the <div> and the <canvas> appear to be created from the id passed into the DeckGL component, id="deck-gl-wrapper" and id="deck-gl" respectively. Where "deck-gl" is the id I passed into the <deckGL> component.
That may or may not be the actual problem, but using the elements inspector in devtools that is my best guess right now.
Can anyone help me figure out why deck.gl and react-map-gl components are not living within their parent bounds? Even when I set the parent and/or canvas props in the DeckGL component?
Documentation links:
react-map-gl
deck.gl
A functioning example is included in the codesandbox linked above. I have included many of the things I have tried as comments but no luck so far.
https://codesandbox.io/s/deck-gl-and-mui-react-e3t23?file=/src/App.js
Thank you...
For local quick reference, the app.js file looks something like this.
import Box from "#material-ui/core/Box";
import DeckGL from "#deck.gl/react";
import { MapView } from "#deck.gl/core";
import { LineLayer } from "#deck.gl/layers";
import { StaticMap } from "react-map-gl";
const MAPBOX_ACCESS_TOKEN = <tokenInCodeSandboxIfYouNeedIt>
const INITIAL_VIEW_STATE = {
longitude: -122.41669,
latitude: 37.7853,
zoom: 13,
pitch: 0,
bearing: 0
};
const data = [
{
sourcePosition: [-122.41669, 37.7853],
targetPosition: [-122.41669, 37.781]
}
];
function App() {
return (
<Box
id='mapcontainer'
sx={{
border: 1,
height: 450,
width: "auto",
m: 5
}}
>
<DeckGL
initialViewState={INITIAL_VIEW_STATE}
controller={true}
id="deck-gl"
>
<LineLayer id="line-layer" data={data} />
<MapView
id="map"
controller={false}
>
<StaticMap
mapStyle="mapbox://styles/mapbox/dark-v9"
mapboxApiAccessToken={MAPBOX_ACCESS_TOKEN}
/>
</MapView>
</DeckGL>
</Box>
);
}
export default App;
In order to allow the deck.gl component to take up the available space of the parent component you can add position: 'relative' into the overrides for your Box with an id of mapcontainer.
I am using the Material ui's tabs and was able to make changes to the Tab's indicator. However, I am trying to reduce the width of the indicator to some fix width for each tab using styles. But it seems the indicator are positioned to left with some calculated value, and giving it a width doesn’t center align the indicator. Couldn't find appropriate solutions, kindly help me out. Here's the editable CodeSandbox.
Find below snippets:
Default fullwidth tabs. Indicator taking full width of the base button:
Width of indicator fixed, but not aligned to center below the tab label.
Based on the docs, you need to attach a span element as the children of the indicator (using TabIndicatorProps). Then you could treat the indicator as a flex container, the span as a flex item with some fixed width.
Here's the component part:
<Tabs
{...other}
classes={{
root: className,
flexContainer: "flexContainer",
indicator: "indicator"
}}
variant="fullWidth"
TabIndicatorProps={{ children: <span /> }}
centered
/>
Here's the styling part:
"& .indicator": {
display: "flex",
justifyContent: "center",
backgroundColor: "transparent",
"& > span": {
maxWidth: 40,
width: "100%",
backgroundColor: "white"
}
}
Demo code
Disclaimer:
I'm new to ReactJS, and web-development as a whole. Did my best to research before, and did not find an answer.
I'm probably missing something simple, but can't figure it out - so sorry if this question's answer is a one liner "How-Did-I-Miss-That' sort of answer.
Feel free to comment/answer with best practices I missed, or things I can improve in this question.
Thanks is advance to anybody that reads this!
My Own Research:
Float 3 Divs - I did not need the z-axis property, as non of my divs are on top of the other.
3 Divs LTR - Talks about 3 divs aligned horizontally, not vertically. The same method did not work for me in the vertical axis.
3 Divs LTR #2 - This talks about flex, so I tried it too. In the right direction, but not enough.
Vertical Align etc - could not make it happen with this solution either.
(5... 1000) A bunch of other first-second-third results in Google search queries like: "ReactJS vertical 3 divs" and the likes.
Actual Question:
Trying to make a basic outline of a mockup web-page, which consists of 3 divs:
Header Div - In The Top, Not Sticky (=when you y axis scroll, it does not appear).
Content Div - In The Middle, Y/X Axis Scrollable.
Bottom Nav Div - In The Bottom, Sticky.
Mockup:
My Current Status:
Can't make my bottom-menu div to appear. it's stuck under the frame.
Can't be sure my bottom-menu div is actually sticky because of the point above.
The contents tab div has no margin from the Header div, which makes the upper end of the text in it - unreadble.
My Code:
Did a lot of back-and-fourth on this, and this is the closest version I have for this simple (yet - not working!) task:
App.jsx
import React from "react";
import BottomMenu from "../BottomMenu/BottomMenu";
import Header from "../Header/Header";
import ContentTab from "../ContentTab/ContentTab";
const App = () => {
return (
<div style = {{display: "flex", flexDirection: "column", overflow: "visible",
direction: "rtl"}}>
<Header/>
<ContentTab />
<BottomMenu />
</div>
);
};
export default App;
Header.jsx
import React from "react";
import { Toolbar, AppBar } from "#material-ui/core";
import Typography from '#material-ui/core/Typography';
const Header = props => {
return (
<div>
<AppBar color="primary" style={{alignItems: 'center'}}>
<Toolbar>
<Typography>
Test
</Typography>
</Toolbar>
</AppBar>
</div>
);
};
export default Header;
ContentTab.jsx
import React from "react";
import Typography from "#material-ui/core/Typography";
import Paper from "#material-ui/core/Paper";
const ContentTab = (props) => {
return (
<div style={{height: "80%", width: "100%"}}>
<Paper align="center" elevation={3}>
<Typography paragraph>First</Typography>
<Typography paragraph>TextTab</Typography>
<Typography paragraph>Last</Typography>
</Paper>
</div>
);
};
export default ContentTab;
BottomMenu.jsx
import React from "react";
import BottomNavigation from "#material-ui/core/BottomNavigation";
import BottomNavigationAction from "#material-ui/core/BottomNavigationAction";
import RestoreIcon from "#material-ui/icons/Restore";
import FavoriteIcon from "#material-ui/icons/Favorite";
import LocationOnIcon from "#material-ui/icons/LocationOn";
import { Toolbar, AppBar } from "#material-ui/core";
export default function BottomMenu() {
const [value, setValue] = React.useState(0);
return (
<div style={{
position: "fixed", bottom: "0", width: "100%", height: "10%"}}>
<AppBar
style={{ background: '#FFFFFF', alignItems: "center" }}
>
<Toolbar>
<BottomNavigation
value={value}
onChange={(event, newValue) => {
setValue(newValue);
}}
showLabels
>
<BottomNavigationAction label="Recents" icon={<RestoreIcon />} />
<BottomNavigationAction label="Favorites" icon={<FavoriteIcon />} />
<BottomNavigationAction label="Nearby" icon={<LocationOnIcon />} />
</BottomNavigation>
</Toolbar>
</AppBar>
</div>
);
}
Actually; the issue is that you're using the Material-UI component AppBar. If this were just a regular DIV tag then you could position it the way you want. To use the AppBar component and make it do what you what then this should do the trick:
remove the outer DIV on the BottomMenu component
style the BottomMenu component's appBar with top of auto and bottom of 0 and give it a position property of fixed.
additionally, style the Header component's appBar with position of static.
this:
in BottomMenu:
<AppBar
position="fixed"
style={{
top: "auto",
bottom: "0",
background: "#FFFFFF",
alignItems: "center"
}}
>
in Header:
<AppBar
position="static"
color="primary"
style={{ alignItems: "center" }}
>
Here's a link to the docs that show it doing what you want:
https://material-ui.com/components/app-bar/
and here's a link to a code sandbox with your code.
https://codesandbox.io/s/material-ui-with-bottom-appbar-ugk31
In general, what I've found with Material-UI is that some of their components have positioning logic built into them and you need to use their properties for positioning instead of trying to do it with CSS.
Basically, I'm creating a form component that is contained inside a v-dialog. The form component will have different child components that are rendered based on select input. So I have to set width of v-dialog to "unset", so that the width of the dialog will stretch to match its content.
The transition works when I toggle the value of width, eg: either 450px or 300px. The problem is that I don't know beforehand the width of the form contains in the dialog, so I definitely need to use dynamic width.
So far, I can not find anyways to achieve transition when using dynamic width. I was trying to get the width of the form component using refs, but setting width to unset, prevent the transition. By the way, the transition I'm talking about is the transition of the width, when using fixed width, it shows nice transition but not for dynamic width
<div id="app">
<v-app id="inspire">
<div class="text-center">
<v-dialog v-model="dialog" width="unset">
<template v-slot:activator="{ on }">
<v-btn color="red lighten-2" dark v-on="on">
Click Me
</v-btn>
</template>
<v-card>
<v-select v-model="selectedForm" :items="items">
</v-select>
<div v-if="selectedForm==='form-a'" class='form-a'>FormA</div>
<div v-if="selectedForm==='form-b'" class='form-b'>FormB</div>
<v-card-actions>
<v-spacer></v-spacer>
<v-btn color="primary" text #click="dialog = false">
I accept
</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
</div>
</v-app>
</div>
new Vue({
el: "#app",
vuetify: new Vuetify(),
data() {
return {
selectedForm: "form-a",
items: ["form-a", "form-b"],
dialog: false
};
}
});
codepen for using fixed width: https://codepen.io/duongthienlee/pen/MWaBLXm
codepen for using dynamic width: https://codepen.io/duongthienlee/pen/GRpBzmL
Noted that in the example i made in codepen, I defined width already, but the real case is that I don't know beforehand the width of form-a and form-b component. form-a and form-b width will be inherited by its parent div which is v-dialog, so that's why I set the width of v-dialog to be unset.
An example of what I mean "dynamic width": form-a has a select input. When user chooses an item, there will be a request to server to get input labels. So form-a will render multiple input fields based on the response body from server. The response body will contain label and default values information. So that makes the width of form-a becomes dynamic.
I think something like this can work for you.
Change v-dialog like so:
<v-dialog v-model="dialog" :width="forms.find(x => x.name===selectedForm).width">
Modify data() to return a forms prop:
data() {
return {
selectedForm: "form-a",
items: ["form-a", "form-b"],
dialog: false,
forms: [
{
name: 'form-a',
width: 200
},
{
name: 'form-b',
width: 1000
}
]
};
}
What you want to do is get the size of the rendered form, and then apply it to the dialog.
This is a common theme when attempting to animate content with dynamic dimensions.
One way to do this is by:
Set the form's visibility as hidden
Wait for it to render
Get the form's width and set it to the dialog
Unset the form's visibility
The tricky/hacky part is that you have to properly await DOM (setTimeout) and Vue ($nextTick) recalculations. I didn't have to await for Vue's $nextTick in this example, but you probably will if you're rendering nested form components:
<div class="form-container">
<div :style="formStyle('form-a')" class='form-a' ref="form-a">FormA</div>
<div :style="formStyle('form-b')" class='form-b' ref="form-b">FormB</div>
</div>
computed:{
formStyle(){
return form => ({
visibility: this.selectedForm == form ? 'inherit' : 'hidden',
position: this.selectedForm == form ? 'inherit' : 'absolute'
})
}
},
methods: {
async onSelectChange(form){
// async request
await new Promise(resolve => setTimeout(resolve, 1000))
this.selectedForm = form
this.recalculate()
},
async recalculate(){
// wait for DOM to recalculate
await new Promise(resolve => setTimeout(resolve))
const formEl = this.$refs[this.selectedForm]
this.dialogWidth = formEl.clientWidth
this.dialogHeight = formEl.clientHeight
},
...
}
Here's the working code to give you an idea:
https://codepen.io/cuzox/pen/yLYwoQo
If I understand you correctly, then this can be done using css. You can try replace all the fix width in the form with
width: fit-content;
For example in you codepen:
.form-a {
width: fit-content;
height: 350px;
background: blue;
}
.form-b {
width: fit-content;
height: 500px;
background: red;
}
The v-dialog renders into a div with class v-dialog:
It seems the animation only works when the the width is of known value, so it cannot be just "unset". The solution would be to get the width of the child element, and set the width of the v-dialog accordingly with a variable.
See VueJS get Width of Div on how to get the width of the child element.
Let me know if it works, I find this is very interesting.