Best practice on creating a reusable Modal component in Svelte? - components

I've been having trouble understanding the best way to create a reusable Modal component in Svelte. I want to have the Modal component where I can pass in other components easily, and in a scalable way. When I click the background, it should destroy the modal and the content inside.
I've created a REPL here with working version but would like to know if this is a convoluted way to achieve this:
https://svelte.dev/repl/9180dd40e7304fc78d175cf4535a6423?version=3.38.2
This method works but I don't know if this is a suggested way (I'm new to both Svelte and component libraries).
App.js
<script>
import Modal from './Modal.svelte'
import Component1 from './Component1.svelte'
import Component2 from './Component2.svelte'
import {showModal} from './store.js'
let showC1, showC2 = false;
function toggleModal() {
$showModal = !$showModal;
}
function toggleC1() {
toggleModal();
showC1 = !showC1;
}
function toggleC2() {
toggleModal();
showC2 = !showC2;}
</script>
<button on:click={toggleC1}>
Toggle Component 1
</button>
<button on:click={toggleC2}>
Toggle Component 2
</button>
<Component1 {showC1} on:click={toggleC1} />
<Component2 {showC2} on:click={toggleC2} />
Modal.svelte
<script>
import {showModal} from './store.js'
</script>
{#if $showModal}
<div on:click|self class='modal'>
<div class='content'>
<slot />
</div>
</div>
{/if}
<style>
.modal {
background-color: rgba(0, 0, 0, 0.589);
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
opacity: 0.7;
display: flex;
justify-content: center;
align-items: center;
}
.content {
background-color: white;
width: 20em;
height: 20em;
}
</style>
Component1.svelte
<script>
import Modal from './Modal.svelte'
export let showC1;
</script>
{#if showC1}
<Modal on:click>
<div>
This is some content...
</div>
<button on:click>
close
</button>
</Modal>
{/if}
Component2.svelte
<script>
import Modal from './Modal.svelte'
export let showC2;
</script>
{#if showC2}
<Modal on:click>
<div>
...And some more content
</div>
<button on:click>
close
</button>
</Modal>
{/if}
store.js
import { writable } from "svelte/store";
export const showModal = writable(false);

I figured out an answer to my question: using svelte:component to make a dynamic component. REPL here:
https://svelte.dev/repl/4624e3f0f3684ddcb2e2da10592f6df1?version=3.38.2

Related

CSS How do I make my div elements lineup perfectly so that collectively they make up a perfect rectangle?

While I am open to any solution, counting tables, Bootstrap and Flexbox, a purely CSS solution using just div elements is greatly appreciated.
HTML
<div class="sentence-summary">
<div class="stat bookmarked">
<i class="fas fa-bookmark"></i>
<span class="count">999</span>
</div>
<div class="stat upvotes">
<i class="fas fa-thumbs-up"></i>
<span class="count">999</span>
</div>
<div class="stat downvotes">
<i class="fas fa-thumbs-down"></i>
<span class="count">999</span>
</div>
<div class="main">
<p>{{ $sentence->body }}</p>
</div>
</div>
SCSS
.sentence-summary {
div {
display: inline-block;
}
.stat {
width: 40px;
text-align: center;
span {
display: block;
font-size: 10px;
}
&.bookmarked {
background-color: red;
}
&.upvotes {
background-color: blue;
}
&.stat.downvotes {
background-color: pink;
}
}
.main {
background-color: green;
}
}
Current Result
Desired Result
I would recommend using a grid layout for this. You can specify that the first three columns (stats) should be 40px wide. And then use '1fr' to say that the 'main' sections should take up the remaining space. Using a grid means that the heights will stay the same.
You can use 'grid-column-gap' to specify the amount of space you would like between each column. Something like this:
.sentence-summary {
display: grid;
grid-template-columns: 40px 40px 40px 1fr;
column-gap: 5px;
grid-auto-rows: 40px;
}
Make sure you use the appropriate browser prefixes as this syntax isn't supported by all browsers. I usually use this auto-prefixer.
Update: Adding grid-auto-rows: 40px; makes sure your 'stats' stay square!

Using Nodejs and React how do you display a Modal dialog from inside a TopNav dropdown

I have a TopNav file which displays the items across the top of my page. When the user clicks their icon a dropdown menu appears. When they select "About" from the menu I want a modal dialog to appear with the information in it.
The closest I have managed to get is by using an actual button instead of the dropdown menu item. Also, pressing the button displays a modal only in the small window area with the dropdown instead of the in the center of the screen.
Here is the code for the TopNav.js
import React, { Component } from "react";
import { connect } from "react-redux";
import { logoutUser } from "../../../actions/authActions";
import { Link, withRouter } from "react-router-dom";
import "./TopNav.scss";
import AboutModal from "../MainContent/AboutModal/AboutModal";
class TopNav extends Component {
constructor() {
super();
this.state = {
isShowing: false
}
}
state = {
dropdown: false
};
componentDidMount() {
document.addEventListener("mousedown", this.handleClick, false);
}
componentWillUnmount() {
document.removeEventListener("mousedown", this.handleClick, false);
}
// Close dropdown when click outside
handleClick = e => {
if (this.state.dropdown && !this.node.contains(e.target)) {
this.setState({ dropdown: !this.state.dropdown });
}
};
onLogoutClick = e => {
e.preventDefault();
this.props.logoutUser(this.props.history);
window.location.href = "/";
};
handleProfileClick = e => {
this.setState({ dropdown: !this.state.dropdown });
};
openModalHandler = () => {
this.setState({
isShowing: true
});
}
closeModalHandler = () => {
this.setState({
isShowing: false
});
}
toggleModal = e => {
this.setState({ modal: !this.state.modal });
};
// Show Side Nav
toggleMenu = e => {
let sideNav = document.querySelector(".side");
sideNav.classList.remove("invisibile");
let hamburger = document.querySelector(".hamburger-top-menu");
hamburger.classList.remove("hamburger-visible");
let rightSide = document.querySelector(".right");
rightSide.classList.remove("no-side");
let rightSideRight = document.querySelector(".right-top");
rightSideRight.classList.remove("right-top-visibile");
};
state = {
show: false,
}
showModal = () => {
this.setState({
...this.state,
show: !this.state.show
});
}
render() {
const { name, email } = this.props.auth.user;
return (
<nav className="top-nav" ref={node => (this.node = node)}>
<div className="left-top">
{ this.state.isShowing ? <div onClick={this.closeModalHandler} className="back-drop"></div> : null }
<i
onClick={this.toggleMenu}
className="material-icons hamburger-top-menu"
>
menu
</i>
<Link to="/dashboard">
<h1 className="brand-header">
TowTech Web
</h1>
</Link>
</div>
<ul className="right-top">
<li>
<div className="email">
<p>Signed in as {email}</p>
</div>
</li>
<li>
<div className="profile" onClick={this.handleProfileClick}>
<span>{name !== undefined && name.split("")[0]}</span>
</div>
{this.state.dropdown ? (
<ul className="dropdown">
<p>Hello, {name !== undefined && name.split(" ")[0]}</p>
<Link to="/dashboard">
<li>Home</li>
</Link>
<Link to="/dashboard">
<li>Profile</li>
</Link>
<Link to="/dashboard">
<li>Administration</li>
</Link>
<Link to="/about">
<li>About</li>
</Link>
<button className="open-modal-btn" onClick={this.openModalHandler}>About Button</button>
<AboutModal
className="modal"
show={this.state.isShowing}
close={this.closeModalHandler}>
This is text for the modal dialog. I can't get it to display in the center of the screen, or with the actual drop down menu item.
</AboutModal>
{/*
<Link to="/tasks">
<li>My Tasks</li>
</Link>
*/}
<li onClick={this.onLogoutClick}>Sign Out</li>
</ul>
) : null}
</li>
</ul>
</nav>
);
}
}
const mapStateToProps = state => ({
auth: state.auth
});
export default connect(
mapStateToProps,
{ logoutUser }
)(withRouter(TopNav));
This is the AboutModal.js
import React from 'react';
import './AboutModal.css';
const modal = (props) => {
return (
<div>
<div className="modal-wrapper"
style={{
transform: props.show ? 'translateY(0vh)' : 'translateY(-100vh)',
opacity: props.show ? '1' : '0'
}}>
<div className="modal-header">
<h3>Modal Header</h3>
<span className="close-modal-btn" onClick={props.close}>×</span>
</div>
<div className="modal-body">
<p>
{props.children}
</p>
</div>
<div className="modal-footer">
<button className="btn-cancel" onClick={props.close}>CLOSE</button>
<button className="btn-continue">CONTINUE</button>
</div>
</div>
</div>
)
}
export default modal;
Here is the AboutModal.css
.modal-wrapper {
background: white;
border: 1px solid #d0cccc;
box-shadow: 0 5px 8px 0 rgba(0,0,0,0.2), 0 7px 20px 0 rgba(0,0,0,0.17);
margin: 100px auto 0;
transition: all .8s;
width: 75%;
}
.modal-header {
background: #263238;
height: 40px;
line-height: 40px;
padding: 5px 20px;
text-align: right;
}
.modal-header h3 {
color: white;
float: left;
margin: 0;
padding: 0;
}
.modal-body {
padding: 10px 15px;
text-align: center;
}
.modal-footer {
background: #263238;
height: 35px;
padding: 15px;
}
.close-modal-btn {
color: white;
cursor: pointer;
float: right;
font-size: 30px;
margin: 0;
}
.close-modal-btn:hover {
color: black;
}
.btn-cancel, .btn-continue {
background: coral;
border: none;
color: white;
cursor: pointer;
font-weight: bold;
outline: none;
padding: 10px;
}
.btn-cancel {
background-color: #b71c1c;
float: left;
}
.btn-continue {
background-color: #1b5e20;
float: right;
}
.back-drop {
background-color: rgba(48, 49, 48, 0.42);
height: 100%;
position: fixed;
transition: all 1.3s;
width: 100%;
}
.open-modal-btn {
margin: 15px;
padding: 10px;
font-weight: bold;
}
There are tons of tutorials and samples out there but all of them use a "Open Modal" button on the main page to display a modal.
It solution ended up being passing the function used in the Dashboardjs handler to the props of the TopNav so that it could execute the function to update the state.
Your "modal" is currently just a div rendered within the dropdown, and you haven't specified a position css property on the element, so it defaults to position: static, which means it's positioned within the flow of the elements around it. If you want it to be positioned relative to the window instead, you can give it position: fixed. Alternatively, you can give it position: absolute, which will position it relative to its nearest positioned (i.e. non-static) ancestor. Since elements default to static position, if you haven't specified a position property (besides static) on any of the parent elements, the "nearest positioned ancestor" will be the viewport. Or if you want the modal to always be positioned relative to another container (e.g. the page container, rather than the viewport), you can set the container's position to relative or similar.
A better option in my opinion would be to render both the nav and the modal as children of the nav's parent component (rather than the modal as a child of the nav component), and use the parent state to manage the modal's show/hide prop.
class Container extends Component {
constructor(props) {
super(props);
this.state = {
modalOpen: false
}
}
toggleModal = () => this.setState(prevState => ({ modalOpen: !prevState.modalOpen }))
render() {
return (
<div>
<TopNav toggleModal={toggleModal} />
<AboutModal show={this.state.modalOpen} />
</div>
)
}
}
This way you can position the modal within a page-height container, instead of positioning it within a nav dropdown.

Three column responsive Masonry grid

I want to show 3 column masonry image grid on Desktop and single column on device. Following plunk works on mobile but it fails on Desktop (lot of gap between two images). I have tried setting width in percent but no luck
https://plnkr.co/edit/g75ClJU4VJWgJbfiKYdu?p=preview
.block{
float: left;
margin: 2px 2px 2px 2px;
width: calc(33.33% - 17px);
}
.block img {
width: 100%;
height: auto;
}
#media only screen and (max-width: 500px) {
.block {
float: left;
margin-bottom: 25px;
width: calc(100% - 17px);
}
}
Thanks in advance
MSK
You will probably want to only make use of media queries and remove float.
The design below is very flexible and allows for
One column on mobile screens
Two columns on tablet
Three columns on small desktop screens / laptops
Four columns on large desktops
Five columns on 4k screens.
The rest of the comments are inside the snippet.
body {
background: #131418;
}
/* Step 1: start with resetting some defaults */
* {
margin: 0 auto;
padding: 0;
max-width: 100%;
}
/* Step 2: center things inside the grid and clear some space around it by setting a device based max-width and margin*/
.grid {
text-align: center;
max-width: 95vw;
margin: 2.5vw auto;
}
/* Step 3: how big should the gap be between grid items? remember that the total gap between two items would be double what you set here since both would have that amount set as their individual padding. Also add box-sizing:border-box to make sure the padding doesn't affect the total widh of the item */
.grid-item {
padding: 5px;
box-sizing: border-box;
}
/* Step 4: Add media queries (subjective) to make the whole grid resposive. */
#media (min-width: 500px) {
.grid-item {
width: 50%;
}
}
#media (min-width: 1000px) {
.grid-item {
width: 33.333%;
}
}
#media (min-width: 1700px) {
.grid-item {
width: 25%;
}
}
#media (min-width: 2100px) {
.grid-item {
width: 20%;
}
}
<!-- Made possible by the great work of David DeSandro # https://masonry.desandro.com -->
<!-- Part 1: Add the scripts -->
<!-- Step 1: Let's start by loading jQuery. jQuery is not required for masonary to function but makes things easier -->
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<!-- Step 2: Then load imagesloaded. imagesloaded makes sure the images are not displayed until they are fully loaded -->
<script src="https://unpkg.com/imagesloaded#4/imagesloaded.pkgd.min.js"></script>
<!-- Step 3: we load masonry -->
<script src="https://unpkg.com/masonry-layout#4/dist/masonry.pkgd.min.js"></script>
<!-- Part 2: Create the grid -->
<!-- Step 1: Start with a the main grid wrapper-->
<div class="grid">
<!-- Step 2: Add grid items--->
<div class="grid-item">
<img src="https://s-media-cache-ak0.pinimg.com/736x/00/37/03/0037037f1590875493f413c1fdbd52b1--cool-beards-inspiring-photography.jpg" />
</div>
<div class="grid-item">
<img src="https://s-media-cache-ak0.pinimg.com/736x/cd/90/d9/cd90d9de63fa2c8e5c5e7117e27b5c18--gritty-portrait-photography-studio-photography.jpg">
</div>
<!-- Step 3: repeat...--->
<div class="grid-item">
<img src="https://1.bp.blogspot.com/-9QM7ciGXRkQ/V1hsB-wNLBI/AAAAAAAAMoA/eYbSHs00PTAjrI4QAmvYAIGCUe1AuRAnwCLcB/s1600/bryan_cranston_0095.jpg">
</div>
<div class="grid-item">
<img src="http://webneel.com/sites/default/files/images/project/best-portrait-photography-regina-pagles%20(10).jpg" />
</div>
<div class="grid-item">
<img src="https://s-media-cache-ak0.pinimg.com/736x/dd/45/96/dd4596b601062eb491ea9bb8e3a78062--two-faces-baby-faces.jpg" />
</div>
<div class="grid-item">
<img src="http://www.marklobo.com.au/news/wp-content/uploads/2013/03/Melbourne_Portrait_Photographer_Mark_Lobo-Cowboy.jpg" />
</div>
<div class="grid-item">
<img src="https://format-com-cld-res.cloudinary.com/image/private/s--PcYqe7Zw--/c_limit,g_center,h_65535,w_960/a_auto,fl_keep_iptc.progressive,q_95/145054-8576001-Rob-Green-by-Zuzana-Breznanikova_7725_b_w.jpg" />
</div>
<div class="grid-item">
<img src="http://www.iefimerida.gr/sites/default/files/janbanning11.jpg" />
</div>
<div class="grid-item">
<img src="https://s-media-cache-ak0.pinimg.com/736x/66/bb/e7/66bbe7acc0d64da627afef440a29714b--portrait-photos-female-portrait.jpg" />
</div>
<div class="grid-item">
<img src="https://s-media-cache-ak0.pinimg.com/736x/25/34/b6/2534b6c18c659546463f13b2dc62d4ce--natural-portraits-female-portraits.jpg" />
</div>
<div class="grid-item">
<img src="https://s-media-cache-ak0.pinimg.com/originals/8d/67/12/8d671230ced871df8428b571ed6ec192.jpg" />
</div>
</div>
<!-- Part 3: the script call -->
<!-- Now that everything is loaded we create a script to trigger masonary on $grid. Note that this simply says: "if the images are fully loaded, trigger masnory on $grid. -->
<script>
var $grid = $(".grid").imagesLoaded(function() {
$grid.masonry({
itemSelector: ".grid-item"
});
});
</script>

How to apply MDL button style to a filePicker?

I would like to know if there is a way to apply the Material Design Lite button style to a file picker, i.e. a component created on an HTML page via:
<input type="file" id="filePicker" />
I would like the "Browse" button of the component to have the look of a Raised button (with ripple if possible). See http://www.getmdl.io/components/#buttons-section.
Thanks!
Using CSS, do you mean something like this?
<style>#file { display: none }</style>
<input type="file" id="file">
<label for="file" class="mdl-button mdl-js-button mdl-button--fab mdl-button--colored">
<i class="material-icons">+</i>
</label>
https://jsfiddle.net/sj838cLg/
Currently, there's no "official" way of doing that. According to the discussion on this issue, the reason is that there's no Material Design specification for that component, so they don't have a guideline to style it. On that same page, the user kybarg provided a CSS/JavaScript code to style a file picker which kind of matches the Material Design specification, so you can use that code:
HTML:
<form>
<div class="mdl-textfield mdl-js-textfield mdl-textfield--floating-label">
<input class="mdl-textfield__input" type="text" />
<label class="mdl-textfield__label">Name</label>
</div>
<div class="mdl-textfield mdl-js-textfield mdl-textfield--floating-label">
<input class="mdl-textfield__input" type="text"/>
<label class="mdl-textfield__label">Position</label>
</div>
<div class="mdl-textfield mdl-js-textfield mdl-textfield--file">
<input class="mdl-textfield__input" placeholder="File" type="text" id="uploadFile" readonly/>
<div class="mdl-button mdl-button--primary mdl-button--icon mdl-button--file">
<i class="material-icons">attach_file</i><input type="file" id="uploadBtn">
</div>
</div>
</form>
CSS:
html {
width: 100%;
}
body {
background: #f5f5f5;
margin: 50px auto;
width: 512px;
}
.mdl-button--file {
input {
cursor: pointer;
height: 100%;
right: 0;
opacity: 0;
position: absolute;
top: 0;
width: 300px;
z-index: 4;
}
}
.mdl-textfield--file {
.mdl-textfield__input {
box-sizing: border-box;
width: calc(100% - 32px);
}
.mdl-button--file {
right: 0;
}
}
JavaScript:
document.getElementById("uploadBtn").onchange = function () {
document.getElementById("uploadFile").value = this.files[0].name;
};
But as there's no official specification, you probably won't find an official implementation from the MDL team for now.

How do I align text/images on bottom/right/center/middle of a container using the blueprint css framework?

Is there some easy way to align stuff in div containers to the right or bottom:
<div class="span-24 align-right last">Text appears on the right side of the layout</div>
or:
<div class="span-2" id="lots-of-content"></div><div class="span-22 last bottom">Appears at bottom of container</div>
or:
<div class="span-24 vertical-middle last">Middle of the container</div>
Here's a sample of what I'm working with trying to position the "topnav" below:
<div class="container">
<div class="span-16">
<h1>Header</h1>
</div>
<div class="span-8 last vertical-middle">
<div id="topnav" class="align-right"><input type="button" id="register" class="ui-button ui-state-default ui-corner-all" value="Register" /> or <button type="button" id="signin" class="ui-button ui-state-default ui-corner-all">Sign in</button></div>
</div>
<hr />
...
</div>
Use position: absolute. For example:
.align-right {
position: absolute;
right: 0;
}
/* Or, alternatively, */
.align-right {
float: right;
}
.bottom {
position: absolute;
bottom: 0;
}
.vertical-middle {
position: absolute;
top: 50%;
}
Note that for vertical-middle, this will center the top edge of the content, no the content itself.
Make sure that the containing DIV is position: relative to force it to become the offset parent (which the position of the children are relative to) EDIT: In other words, add the following rule:
.container {
position: relative;
}

Resources