Search code examples
reactjsbootstrap-4next.jsreact-bootstrap

Using React-Bootstrap, how can i keep my Instrument Cards dynamically sized, while still specifying a minimum card size


So, here's my predicament

Here's what my cards currently look like:

enter image description here

Which looks all fine and dandy, however, when you change the screen size, this is what it looks like:

enter image description here

Which isn't great, because we want bootstrap apps, and really any website to be responsive these days.

What i want is for the cards to horizontally stretch, that way no matter what the width of our display is the cards will fill up the entire card deck - which is that bottom section titled 'Instrument List'

Now, if i put

style={{flexGrow: 1}} 

onto each of the cards, then it seems to kind of work, however, not work as i really want it to.

If i have a row of cards that doesen't contain 5 cards, it will stretch them out:

enter image description here

I just want my card deck to stretch to fill the full container, but i also want it to keep a standard size for my cards

Here is the code i have for the Card Deck that generates the instruments from a JSON file, and the Card items.

The list code:

import InstrumentCard from './InstrumentCard'
import styles from '../styles/InstrumentList.module.scss'
import {CardDeck} from 'react-bootstrap'


const InstrumentList = ({instruments, onDel, onLoc, onShuf, onRep, onRepButClick, setRepInstrumentID}) => {
    
    return (
            <CardDeck key={1} className={styles.container} style={{display: 'flex', flexDirection: 'row'}}>
                {instruments.map((instrument, i) => (
                    <InstrumentCard
                        key={i} 
                        id={i}
                        instr = {instrument}
                        name={instrument.name} 
                        description={instrument.description}
                        imagePath={instrument.image}
                        wikiLink={instrument.wikipedia}
                        tubeLink={instrument.youtube}
                        onDelete = {onDel}
                        isLocked = {instrument.locked}
                        onLock = {onLoc}
                        onShuffle = {onShuf}
                        onReplace = {onRep}
                        onRepButtonClick = {onRepButClick}
                        setReplacementInstrumentID = {setRepInstrumentID}
                        style={{flex: 1}}>
                            
                    </InstrumentCard>
                )) }
            </CardDeck>
    )
}

export default InstrumentList

The card code:

import {Card, Button, ButtonGroup, ButtonToolbar} from 'react-bootstrap'
import styles from '../styles/InstrumentCard.module.scss'
import {useState} from 'react'

const InstrumentCard = ({id, name, description, imagePath, wikiLink, tubeLink, onDelete, isLocked, onLock, onShuffle, setReplacementInstrumentID, onRepButtonClick}) => {
    
    return (
            <Card style={{ minWidth: '18rem', flexGrow: 1, margin:'1rem', minHeight:'32rem'}}>
                
                <div>
                    <button id="lockButton" onClick={() => {onLock(id)}} className={isLocked ? styles.btnOverrideLocked : styles.btnOverride}>{isLocked ? "🔒" : "🔓"}</button>
                    <button id="shuffleButton" autoFocus={true} onClick={()=> {onShuffle(id)}} className={styles.btnOverrideShuffle} style={{float: "right"}}><span>🔀</span></button>
                </div>
                <Card.Img variant="top" src={imagePath} style={{padding:'1rem', maxHeight: '10rem', height:'100%', objectFit: 'contain'}} />
                <Card.Body style={{display: 'flex', flexDirection: 'column', justifyContent: 'space-between'}}>
                    <div>
                    <Card.Title>{name}</Card.Title>
                    <Card.Text>
                        {description}
                    </Card.Text>
                    </div>
                    <div style={{marginTop: '.5rem'}}>
                    <ButtonGroup style={{width: "100%"}}>
                        <Button variant="danger" onClick={()=> onDelete(id)}>Delete</Button>
                        <Button variant="secondary" onClick={()=> {setReplacementInstrumentID(id); onRepButtonClick()}} >Replace</Button>
                    </ButtonGroup>
                    </div>
                </Card.Body>
                <Card.Footer>
                    <Card.Link target="_blank" rel="noopener noreferrer" href={wikiLink}>Wikipedia</Card.Link>
                    <Card.Link target="_blank" rel="noopener noreferrer" href={tubeLink}>Youtube</Card.Link>
                </Card.Footer>
            </Card>
    )
}

export default InstrumentCard

Any help or explanations are very much appreciated! Thank you in advance!

Edit: Something i tried and failed was using map and creating a variable "let newIndex = (i*5) and attempt to use like newIndex + 1 to get to the other indexes, and try and generate 5 bootstrap columns within a row, however, this does not work, atleast with the map function, as there is no way to stop map from overshooting the actual amount of instruments, among other issues.

I have also tried adding a max width and height to my cards, however, that still allows them to grow and shrink by row, which we dont want

I feel like some kind of bootstrap Col and Row generation would be ideal, but i have no idea how to get it to generate only 5 elements per row using javascript in the return statement.


Solution

  • I have figured out a solution!

    So, basically in my InstrumentList, i forgo the use of a card deck entirely, and just use a Row element instead. Inside the instrument.map, i surround every instrument card with a Col element that i then apply some custom styling to, because i want exactly five columns, and 12 does not divide by 5, so i had to fudge it a little.

    Here's my final InstrumentList

    import InstrumentCard from './InstrumentCard'
    import styles from '../styles/InstrumentList.module.scss'
    import {CardDeck, Row, Col} from 'react-bootstrap'
    
    
    const InstrumentList = ({instruments, onDel, onLoc, onShuf, onRep, onRepButClick, setRepInstrumentID}) => {
        
        return (
                <Row key={1} className={styles.container} style={{display: 'flex', flexDirection: 'row', flexWrap: 'wrap', justifyContent: 'flex-start' }}>
                    {instruments.map((instrument, i) => (
                        <Col className={styles.properCol}>
                        <InstrumentCard
                            key={i} 
                            id={i}
                            instr = {instrument}
                            name={instrument.name} 
                            description={instrument.description}
                            imagePath={instrument.image}
                            wikiLink={instrument.wikipedia}
                            tubeLink={instrument.youtube}
                            onDelete = {onDel}
                            isLocked = {instrument.locked}
                            onLock = {onLoc}
                            onShuffle = {onShuf}
                            onReplace = {onRep}
                            onRepButtonClick = {onRepButClick}
                            setReplacementInstrumentID = {setRepInstrumentID}
                            style={{flex: 1}}>
                                
                        </InstrumentCard>
                        </Col>
                    )) }
                   
                </Row>
        )
    }
    
    export default InstrumentList
    

    Final InstrumentCard

    import {Card, Button, ButtonGroup, Col} from 'react-bootstrap'
    import styles from '../styles/InstrumentCard.module.scss'
    import {useState} from 'react'
    
    const InstrumentCard = ({id, name, description, imagePath, wikiLink, tubeLink, onDelete, isLocked, onLock, onShuffle, setReplacementInstrumentID, onRepButtonClick}) => {
        
        return (
                
                <Card style={{minWidth: '16rem', flexGrow: 1, margin:'1rem', minHeight:'32rem'}}>
                    <div>
                        <button id="lockButton" onClick={() => {onLock(id)}} className={isLocked ? styles.btnOverrideLocked : styles.btnOverride}>{isLocked ? "🔒" : "🔓"}</button>
                        <button id="shuffleButton" autoFocus={true} onClick={()=> {onShuffle(id)}} className={styles.btnOverrideShuffle} style={{float: "right"}}><span>🔀</span></button>
                    </div>
                    <Card.Img variant="top" src={imagePath} style={{padding:'1rem', maxHeight: '10rem', height:'100%', objectFit: 'contain'}} />
                    <Card.Body style={{display: 'flex', flexDirection: 'column', justifyContent: 'space-between'}}>
                        <div>
                        <Card.Title>{name}</Card.Title>
                        <Card.Text>
                            {description}
                        </Card.Text>
                        </div>
                        <div style={{marginTop: '.5rem'}}>
                        <ButtonGroup style={{width: "100%"}}>
                            <Button variant="danger" onClick={()=> onDelete(id)}>Delete</Button>
                            <Button variant="secondary" onClick={()=> {setReplacementInstrumentID(id); onRepButtonClick()}} >Replace</Button>
                        </ButtonGroup>
                        </div>
                    </Card.Body>
                    <Card.Footer>
                        <Card.Link target="_blank" rel="noopener noreferrer" href={wikiLink}>Wikipedia</Card.Link>
                        <Card.Link target="_blank" rel="noopener noreferrer" href={tubeLink}>Youtube</Card.Link>
                    </Card.Footer>
                </Card>
        )
    }
    
    export default InstrumentCard
    

    And the styling for InstrumentList

    .container {
        margin: 2rem;
    }
    
    .properCol {
        flex: 0 0 20%;
        max-width: 20%;
        position: relative;
        width: 100%;
        padding: 0;
    }
    
    @media (max-width: 1400px) {
        .properCol {
            flex: 0 0 25%;
            max-width: 25%;
            position: relative;
            width: 25%;
            padding: 0;
        }
    }
    
    @media (max-width: 1150px) {
        .properCol {
            flex: 0 0 33.333333%;
            max-width: 33.333333%;
            position: relative;
            width: 33.333333%;
            padding: 0;
        }
    }
    
    @media (max-width: 900px) {
        .properCol {
            flex: 0 0 50%;
            max-width: 50%;
            position: relative;
            width: 50%;
            padding: 0;
        }
    }
    
    @media (max-width: 620px) {
        .properCol {
            flex: 0 0 100%;
            max-width: 100%;
            position: relative;
            width: 100%;
            padding: 0;
        }
    }
    

    Sincerely hope this helps someone else in the future! One of the biggest PITA ive had to deal with in awhile. Cheers!