I've developed the following React DropdownList component using styled-components and flexbox:
import React from "react";
import PropTypes from "prop-types";
import styled from "styled-components";
import { FontAwesomeIcon } from "#fortawesome/react-fontawesome";
import { faCaretDown } from "#fortawesome/free-solid-svg-icons";
const options = [
{
key: "opt1",
value: "Option 1",
},
{
key: "opt2",
value: "Option 2",
},
{
key: "opt3",
value: "Option 3",
},
{
key: "opt4",
value: "Option 4",
},
{
key: "opt5",
value: "Option 5",
},
{
key: "opt6",
value: "Option 6",
},
{
key: "opt7",
value: "Option 7",
},
];
const DropdownListContainer = styled.div`
position: relative;
display: inline-block;
${(props) =>
props.disabled &&
`
opacity: 0.6;
`}
`;
const DropdownListButton = styled.button`
display: flex;
align-items: flex-start;
width: 100%;
background-color: white;
padding: ${(props) =>
props.collapse ? "7px 8px 7px 8px" : "8px 8px 8px 8px"};
border: 1px solid black;
border-radius: 4px;
cursor: pointer;
overflow: hidden;
}`;
const DropdownListButtonTitle = styled.div`
display: flex;
justify-content: space-between;
align-items: center;
width: 100%;
}`;
const DropdownListContentDiv = styled.div`
display: ${(props) => (props.collapsed ? "none" : "absolute")};
min-width: 160px;
position: relative;
background-color: white;
border: 1px solid #fefefe;
min-width: 160px;
box-shadow: 0px 8px 16px 0px rgba(0,0,0,0.2);
z-index: 10;
}`;
const OptionDiv = styled.div`
width: 100%;
padding: 4px 0px 4px 4px;
overflow: hidden;
&:hover {
background-color: #020202;
color: white;
}
`;
class DropdownList extends React.Component {
static propTypes = {
onChange: PropTypes.func,
onDropdownListClick: PropTypes.func,
value: PropTypes.any,
options: PropTypes.array,
disabled: PropTypes.bool,
large: PropTypes.bool,
};
state = {
collapsed: true,
};
handleButtonClick = () => {
this.setState({
collapsed: !this.state.collapsed,
});
};
handleSelect = (id) => {
if (this.props.onChange) this.props.onChange(id);
this.setState({
collapsed: true,
});
};
render = () => {
let { value, disabled, large, readOnly } = this.props;
// let { options } = this.props; // Using hardcoded options for testing
let { collapsed } = this.state;
let optionsList = [];
let val = null;
if (options && options.length > 0) {
options.forEach((option) => {
let content = option.value;
optionsList.push(
<OptionDiv
key={option.key}
onClick={() => this.handleSelect(option.key)}
large={large}
>
{content}
</OptionDiv>
);
if (value === option.key) val = option.value;
});
}
if (!val && options && options.length > 0) val = options[0].value;
let buttonContent = (
<DropdownListButtonTitle>
<div>{val}</div>
<div>
<FontAwesomeIcon icon="faCaretDown" size="small" />
</div>
</DropdownListButtonTitle>
);
return (
<DropdownListContainer disabled={disabled}>
<DropdownListButton
onClick={this.handleButtonClick}
disabled={readOnly}
>
{buttonContent}
</DropdownListButton>
<DropdownListContentDiv collapsed={collapsed}>
{optionsList}
</DropdownListContentDiv>
</DropdownListContainer>
);
};
}
export default DropdownList;
It is working fine, except for a styling problem. If I use inside a content, when I open the menu it shifts all content down. The same behaviour happens when I use it at the end of the view screen. It increases the view height and pops up a scrollbar.
I do expect it to "float" over the content, showing the content above it. At the edges of the screen I expect it to open in the opposite direction (up if I'm at the screen bottom and left if I'm at the screen rightmost position).
DropdownListContentDiv shouldn't get display "none" or "absolute", it should get display "none" or "block". After that, position of that content is not "relative", but "absolute", like this:
const DropdownListContentDiv = styled.div`
display: ${(props) => (props.collapsed ? "none" : "block")};
min-width: 160px;
position: absolute;
background-color: white;
border: 1px solid #fefefe;
min-width: 160px;
box-shadow: 0px 8px 16px 0px rgba(0,0,0,0.2);
z-index: 10;
}`;
Related
I am trying to build a component where you can visualize on what section are you in and allows you to easily move sections on the page.
My page is structured like this
<SectionManager /> // the absolutely positioned element
<Navbar />
<Menu />
<Section1 />
<Section2 />
<Section3 />
{...}
I want to have every even numbered section to have a black background and the others to have a white background. Now I want the text inside my SectionManager component to be white when overlapping a black background and black when overlapping a white background.
Here is a photo:
My component is the one on the left. And when you scroll down to the black section I want just the about me text and the circle after that to turn white.
Sorry if this is a stupid question by I searched for hours and did not find anything. I tried mix-blend-mode but it did not work.
Here the code for my component:
const SectionManager: React.FC = () => {
const globalState = React.useContext(MyContext);
const observerCallback = (entries: IntersectionObserverEntry[]) => {
...
};
const observerOptions = React.useMemo(
...
);
React.useEffect(() => {
const observer = new IntersectionObserver(...);
globalState.currentSections.forEach((section: HTMLElement) => {
observer.observe(section);
});
}, []);
const sections = [
{
text: "Hello!",
},
{
text: "about me",
},
{
text: "work i did",
},
{
text: "contact",
},
];
return (
<div className={styles.sectionManager}>
{sections.map((section, sectionID) => (
<>
{sectionID > 0 && (
<div className={styles.sectionManager_separator}></div>
)}
<div
className={
sectionID === globalState.activeSectionId
? `${styles.sectionManager_item} ${styles.sectionManager_itemActive}`
: styles.sectionManager_item
}
>
<p>{section.text}</p>
</div>
</>
))}
</div>
);
};
export default SectionManager;
here is the scss file:
.sectionManager {
position: fixed;
z-index: 100;
right: 30px;
top: 50%;
display: flex;
flex-direction: column;
align-items: flex-end;
transform: translateY(-50%);
&_separator {
width: 1px;
height: 25px;
background: $text-secondary-dark;
margin-right: 7px;
}
&_itemActive {
&::after {
background-color: $text-primary-light !important;
transform: scale(1) !important;
}
p {
color: $text-primary-light !important;
transform: scaleX(1) !important;
}
}
&_item {
mix-blend-mode: difference;
display: flex;
align-items: center;
font-size: 1rem;
margin-top: 5px;
cursor: pointer;
background: transparent;
#include transition();
&:hover {
&::after {
background-color: $text-primary-light;
transform: scale(1);
}
p {
transform: scaleX(1);
color: $text-primary-light;
}
}
p {
margin: 0;
transform: scaleX(0);
transform-origin: right;
color: $text-secondary-light;
#include transition();
}
&::after {
content: "";
width: 15px;
height: 15px;
border-radius: 999999px;
margin-left: 10px;
transform: scale(0.9);
background: $text-secondary-dark;
#include transition();
}
}
}
And for the section background I am not doing anything fancy, I am just setting a background-color property on there.
Thank you in advance!
Edit:
I want something similar to that. The design is in figma.
I solved the issue!
I ended up getting all the sections on my page using querySelector and using an IntersectionObserver to get the section that is in viewPort and get its background color, then passing the background color to my component using data-section-bg.
Here is the whole component code:
const SectionManager: React.FC = () => {
const [currentSectionBg, setCurrentSectionsBg] =
React.useState<string>("#fff");
const globalState = React.useContext(MyContext);
const observerCallback = (entries: IntersectionObserverEntry[]) => {
// other observer ...
};
const sectionColorObserverCallback = (
entries: IntersectionObserverEntry[]
) => {
entries.forEach((entry) => {
if (entry.intersectionRatio > 0.25) {
const sectionBgColor = (
document.getElementById(entry.target.id) as HTMLElement
).style.backgroundColor;
console.log(sectionBgColor);
setCurrentSectionsBg(sectionBgColor);
}
});
};
const observerOptions = React.useMemo(
() => ({
root: null,
rootMargin: "0px",
threshold: 0.25,
}),
[]
);
React.useEffect(() => {
const observer = new IntersectionObserver(
// other observer...
);
globalState.currentSections.forEach((section: HTMLElement) => {
// other observer...
});
// Detect Section Color Observer
const allSections = document.querySelectorAll("section");
if (!allSections) return;
const sectionColorObserver = new IntersectionObserver(
sectionColorObserverCallback,
observerOptions
);
allSections.forEach((section, sectionId) => {
section.id = `SECTION_${sectionId}`;
section.style.backgroundColor = sectionId % 2 === 0 ? "#fff" : "#000";
sectionColorObserver.observe(section);
});
}, []);
const sections = [
{
text: "Hello!",
},
{
text: "about me",
},
{
text: "work i did",
},
{
text: "contact",
},
];
return (
<motion.div
initial={{ x: 150, opacity: 0 }}
animate={{
x: 0,
y: "-50%",
opacity: 1,
transition: {
duration: 1,
delay: 0.8,
ease: defaultAnimationEasing,
},
}}
className={styles.sectionManager}
>
{sections.map((section, sectionID) => (
<>
{sectionID > 0 && (
<div className={styles.sectionManager_separator}></div>
)}
<div
data-section-bg={currentSectionBg}
className={
sectionID === globalState.activeSectionId
? `${styles.sectionManager_item} ${styles.sectionManager_itemActive}`
: styles.sectionManager_item
}
>
<p>{section.text}</p>
</div>
</>
))}
</motion.div>
);
};
export default SectionManager;
Here is what I added to my scss File:
[data-section-bg="rgb(0, 0, 0)"] {
&:hover {
&::after {
background: $text-primary-dark !important;
}
p {
color: $text-primary-dark;
}
}
p {
color: $text-primary-dark;
}
}
[data-section-bg="rgb(255, 255, 255)"] {
&:hover {
&::after {
background: $text-primary-light !important;
}
p {
color: $text-primary-light;
}
}
p {
color: $text-primary-light;
}
}
&_itemActive[data-section-bg="rgb(255, 255, 255)"] {
&::after {
background-color: $text-primary-light !important;
}
}
&_itemActive[data-section-bg="rgb(0, 0, 0)"] {
&::after {
background-color: $text-primary-dark !important;
}
}
I'm working on customisable dashboard where (amongst other features) users can drag dashboard tiles (div elements) around and reposition those tiles anywhere in the dashboard.
HTML Structure
The html structure is similar to the snippet below
<div class="dashboard">
<div class="tile"><canvas/></div>
<div class="tile"><canvas/></div>
<div class="tile empty"></div>
</div>
Expected Behavior
The idea is that the .dashboard can contain multiple .tiles and each .tile contains a report (a graph/chart drawn on a canvas element). Some of these .tiles can be .empty, as in, not containing any report. Then .tile can be dragged and dropped into the .empty tiles.
So, div.tile.empty serves as "dropzone" while div.tile will be draggable elements. A fiddler snippet has been added below for a simplistic-but-fully-functional example.
Libraries used
jQuery
ChartJs. An open source js library to draw charts on a canvas
The problem
It all seems to work well, except that after dropping a .tile the canvas resets its width/height to zero!!! And I haven't found a way to reset it back to its original dimensions before the drag/drop events. Even if I set the width/height manually, nothing is drawn on the canvas.
Question
Is there any way I can preserve (or recover) the width/height of the canvas while also getting it to re-drawn the graph after drag/dropping?
I tried using chartjs's update, render and resize functions to no avail.
The documentation of these functions can be found in the link below (version 3.5.0)...
https://www.chartjs.org/docs/3.5.0/developers/api.html
Code Example
Here's a sample code snippet where you can reproduce the issue mentioned above. The buttons are my attempt to update/resize/re-render the graphs after drag/dropping.
var $sourceTile = null;
var charts = [];
$(() => {
$(".buttons-container a").on("click", btnClickHandler);
renderChart("canvas1", 'doughnut');
renderChart("canvas2", "line");
attachDropHandlers();
});
attachDropHandlers = () => {
$(".tile").off("dragstart").off("dragover").off("drop");
$(".tile .report").on("dragstart", dragStartHandler);
$(".tile.empty").on("dragover", dragOverHandler);
$(".tile.empty").on("drop", dropHandler);
}
dragStartHandler = (e) => {
const $target = $(e.target);
const $report = $target.is(".report") ? $target : $target.parents(".report");
$sourceTile = $report.parents(".tile");
e.originalEvent.dataTransfer.setData("application/dashboard", $report[0].id);
e.originalEvent.dataTransfer.effectAllowed = "move";
e.originalEvent.dataTransfer.dropEffect = "move";
}
dragOverHandler = (e) => {
e.preventDefault();
e.originalEvent.dataTransfer.dropEffect = "move";
}
dropHandler = (e) => {
e.preventDefault();
const id = e.originalEvent.dataTransfer.getData("application/dashboard");
if (id) {
$("#" + id).appendTo(".tile.empty");
$(".tile.empty").removeClass("empty");
if ($sourceTile) {
$sourceTile.addClass("empty");
attachDropHandlers();
}
}
}
renderChart = (canvasId, type) => {
const labels = ["Red", "Green", "Blue"];
const data = [30, 25, 42];
const colors = ['rgba(255, 99, 132, 1)', 'rgba(54, 162, 235, 1)', 'rgba(255, 206, 86, 1)'];
const canvas = document.getElementById(canvasId);
const ctx = canvas.getContext('2d');
const chart = new Chart(ctx, {
type: type,
data: {
labels: labels,
datasets: [{
data: data,
backgroundColor: colors,
borderColor: colors,
borderWidth: 1
}]
},
options: {
responsive: true,
maintainAspectRatio: true,
aspectRatio: 1,
plugins: {
legend: {
display: false
},
htmlLegend: {
tile: this.$tile,
maxItems: 8
}
}
}
});
chart.update();
charts.push(chart);
}
btnClickHandler = (e) => {
const button = e.target.id;
switch (button) {
case "btn1":
charts.forEach((chart) => chart.update());
break;
case "btn2":
charts.forEach((chart) => chart.update('resize'));
break;
case "btn3":
charts.forEach((chart) => chart.render());
break;
case "btn4":
charts.forEach((chart) => chart.resize());
break;
case "btn5":
charts.forEach((chart) => chart.resize(120, 120));
break;
}
}
html,
body {
background-color: #eee;
}
h3 {
margin: 0;
padding: 10px;
}
.dashboard {}
.dashboard .tile {
display: inline-block;
vertical-align: top;
margin: 5px;
height: 250px;
width: 250px;
}
.tile.empty {
border: 2px dashed #ccc;
}
.report {
height: 250px;
width: 250px;
background-color: #fff;
border-radius: 3px;
box-shadow: 0 1px 2px rgba(0, 0, 0, .18);
}
.buttons-container {
display: flex;
justify-content: space-between;
margin: 20px 0;
}
.buttons-container a {
background-color: #673AB7;
color: #EDE7F6;
cursor: pointer;
padding: 10px 15px;
border-radius: 3px;
box-shadow: 0 1px 2px rgba(0, 0, 0, .18);
}
.buttons-container a:hover {
background-color: #7E57C2;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/2.2.4/jquery.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/chart.js#3.6.0/dist/chart.min.js"></script>
<div class="dashboard">
<div class="tile">
<div id="report1" class="report" draggable="true">
<h3>
Report 1
</h3>
<div style="padding:10px;height:180px;width:180px">
<canvas id="canvas1"></canvas>
</div>
</div>
</div>
<div class="tile">
<div id="report2" class="report" draggable="true">
<h3>
Report 2
</h3>
<div style="padding:10px;height:180px;width:180px">
<canvas id="canvas2"></canvas>
</div>
</div>
</div>
<div class="tile empty">
</div>
</div>
<div class="buttons-container">
<a id="btn1">update()</a>
<a id="btn2">update('resize')</a>
<a id="btn3">render()</a>
<a id="btn4">resize()</a>
<a id="btn5">resize(120,120)</a>
</div>
This is a Chart.js issue of version 3.6.0 and fixed in version 3.6.1. Example below:
var $sourceTile = null;
var charts = [];
$(() => {
$(".buttons-container a").on("click", btnClickHandler);
renderChart("canvas1", 'doughnut');
renderChart("canvas2", "line");
attachDropHandlers();
});
attachDropHandlers = () => {
$(".tile").off("dragstart").off("dragover").off("drop");
$(".tile .report").on("dragstart", dragStartHandler);
$(".tile.empty").on("dragover", dragOverHandler);
$(".tile.empty").on("drop", dropHandler);
}
dragStartHandler = (e) => {
const $target = $(e.target);
const $report = $target.is(".report") ? $target : $target.parents(".report");
$sourceTile = $report.parents(".tile");
e.originalEvent.dataTransfer.setData("application/dashboard", $report[0].id);
e.originalEvent.dataTransfer.effectAllowed = "move";
e.originalEvent.dataTransfer.dropEffect = "move";
}
dragOverHandler = (e) => {
e.preventDefault();
e.originalEvent.dataTransfer.dropEffect = "move";
}
dropHandler = (e) => {
e.preventDefault();
const id = e.originalEvent.dataTransfer.getData("application/dashboard");
if (id) {
$("#" + id).appendTo(".tile.empty");
$(".tile.empty").removeClass("empty");
if ($sourceTile) {
$sourceTile.addClass("empty");
attachDropHandlers();
}
}
}
renderChart = (canvasId, type) => {
const labels = ["Red", "Green", "Blue"];
const data = [30, 25, 42];
const colors = ['rgba(255, 99, 132, 1)', 'rgba(54, 162, 235, 1)', 'rgba(255, 206, 86, 1)'];
const canvas = document.getElementById(canvasId);
const ctx = canvas.getContext('2d');
const chart = new Chart(ctx, {
type: type,
data: {
labels: labels,
datasets: [{
data: data,
backgroundColor: colors,
borderColor: colors,
borderWidth: 1
}]
},
options: {
responsive: true,
maintainAspectRatio: true,
aspectRatio: 1,
plugins: {
legend: {
display: false
},
htmlLegend: {
tile: this.$tile,
maxItems: 8
}
}
}
});
chart.update();
charts.push(chart);
}
btnClickHandler = (e) => {
const button = e.target.id;
switch (button) {
case "btn1":
charts.forEach((chart) => chart.update());
break;
case "btn2":
charts.forEach((chart) => chart.update('resize'));
break;
case "btn3":
charts.forEach((chart) => chart.render());
break;
case "btn4":
charts.forEach((chart) => chart.resize());
break;
case "btn5":
charts.forEach((chart) => chart.resize(120, 120));
break;
}
}
html,
body {
background-color: #eee;
}
h3 {
margin: 0;
padding: 10px;
}
.dashboard {}
.dashboard .tile {
display: inline-block;
vertical-align: top;
margin: 5px;
height: 250px;
width: 250px;
}
.tile.empty {
border: 2px dashed #ccc;
}
.report {
height: 250px;
width: 250px;
background-color: #fff;
border-radius: 3px;
box-shadow: 0 1px 2px rgba(0, 0, 0, .18);
}
.buttons-container {
display: flex;
justify-content: space-between;
margin: 20px 0;
}
.buttons-container a {
background-color: #673AB7;
color: #EDE7F6;
cursor: pointer;
padding: 10px 15px;
border-radius: 3px;
box-shadow: 0 1px 2px rgba(0, 0, 0, .18);
}
.buttons-container a:hover {
background-color: #7E57C2;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/2.2.4/jquery.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/chart.js#3.6.1/dist/chart.min.js"></script>
<div class="dashboard">
<div class="tile">
<div id="report1" class="report" draggable="true">
<h3>
Report 1
</h3>
<div style="padding:10px;height:180px;width:180px">
<canvas id="canvas1"></canvas>
</div>
</div>
</div>
<div class="tile">
<div id="report2" class="report" draggable="true">
<h3>
Report 2
</h3>
<div style="padding:10px;height:180px;width:180px">
<canvas id="canvas2"></canvas>
</div>
</div>
</div>
<div class="tile empty">
</div>
</div>
<div class="buttons-container">
<a id="btn1">update()</a>
<a id="btn2">update('resize')</a>
<a id="btn3">render()</a>
<a id="btn4">resize()</a>
<a id="btn5">resize(120,120)</a>
</div>
I have this html file
<html lang="en" class=""><head>
<meta charset="UTF-8">
<title>CodePen Demo</title>
<meta name="robots" content="noindex">
<link rel="shortcut icon" type="image/x-icon" href="https://static.codepen.io/assets/favicon/favicon-aec34940fbc1a6e787974dcd360f2c6b63348d4b1f4e06c77743096d55480f33.ico">
<link rel="mask-icon" type="" href="https://static.codepen.io/assets/favicon/logo-pin-8f3771b1072e3c38bd662872f6b673a722f4b3ca2421637d5596661b4e2132cc.svg" color="#111">
<link rel="canonical" href="https://codepen.io/hardkoded/pen/VwjvWBm">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/normalize/5.0.0/normalize.min.css">
<style class="INLINE_PEN_STYLESHEET_ID">
body {
font: 14px "Century Gothic", Futura, sans-serif;
margin: 20px;
}
ol, ul {
padding-left: 30px;
}
.board-row:after {
clear: both;
content: "";
display: table;
}
.status {
margin-bottom: 10px;
}
.square {
background: #fff;
border: 1px solid #999;
float: left;
font-size: 24px;
font-weight: bold;
line-height: 34px;
height: 34px;
padding: 0;
text-align: center;
width: 34px;
}
.square:focus {
outline: none;
}
.kbd-navigation .square:focus {
background: #ddd;
}
.game {
display: flex;
flex-direction: row;
}
.game-info {
margin-left: 20px;
}
</style>
<script src="https://static.codepen.io/assets/editor/iframe/iframeConsoleRunner-7f4d47902dc785f30dedcac9c996b9f31d4dfcc33567cc48f0431bc918c2bf05.js"></script>
<script src="https://static.codepen.io/assets/editor/iframe/iframeRefreshCSS-e03f509ba0a671350b4b363ff105b2eb009850f34a2b4deaadaa63ed5d970b37.js"></script>
<script src="https://static.codepen.io/assets/editor/iframe/iframeRuntimeErrors-29f059e28a3c6d3878960591ef98b1e303c1fe1935197dae7797c017a3ca1e82.js"></script>
</head>
<body class="mouse-navigation">
<div id="errors" style="
background: #c00;
color: #fff;
display: none;
margin: -20px -20px 20px;
padding: 20px;
white-space: pre-wrap;
"></div>
<div id="root"><div class="game"><div class="game-board"><div><div class="board-row"><button class="square"></button><button class="square"></button><button class="square"></button></div><div class="board-row"><button class="square"></button><button class="square"></button><button class="square"></button></div><div class="board-row"><button class="square"></button><button class="square"></button><button class="square"></button></div></div></div><div class="game-info"><div id="status">Next player: X</div><ol><li><button>Go to game start</button></li></ol></div></div></div>
<script>
window.addEventListener('mousedown', function(e) {
document.body.classList.add('mouse-navigation');
document.body.classList.remove('kbd-navigation');
});
window.addEventListener('keydown', function(e) {
if (e.keyCode === 9) {
document.body.classList.add('kbd-navigation');
document.body.classList.remove('mouse-navigation');
}
});
window.addEventListener('click', function(e) {
if (e.target.tagName === 'A' && e.target.getAttribute('href') === '#') {
e.preventDefault();
}
});
window.onerror = function(message, source, line, col, error) {
var text = error ? error.stack || error : message + ' (at ' + source + ':' + line + ':' + col + ')';
errors.textContent += text + '\n';
errors.style.display = '';
};
console.error = (function(old) {
return function error() {
errors.textContent += Array.prototype.slice.call(arguments).join(' ') + '\n';
errors.style.display = '';
old.apply(this, arguments);
}
})(console.error);
</script>
<script src="https://static.codepen.io/assets/common/stopExecutionOnTimeout-157cd5b220a5c80d4ff8e0e70ac069bffd87a61252088146915e8726e5d9f147.js"></script>
<script src="https://unpkg.com/react#16/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom#16/umd/react-dom.development.js"></script>
<script id="INLINE_PEN_JS_ID">
function Square(props) {
return (
React.createElement("button", { className: "square", onClick: props.onClick },
props.value));
}
class Board extends React.Component {
renderSquare(i) {
return (
React.createElement(Square, {
value: this.props.squares[i],
onClick: () => this.props.onClick(i) }));
}
render() {
return (
React.createElement("div", null,
React.createElement("div", { className: "board-row" },
this.renderSquare(0),
this.renderSquare(1),
this.renderSquare(2)),
React.createElement("div", { className: "board-row" },
this.renderSquare(3),
this.renderSquare(4),
this.renderSquare(5)),
React.createElement("div", { className: "board-row" },
this.renderSquare(6),
this.renderSquare(7),
this.renderSquare(8))));
}}
class Game extends React.Component {
constructor(props) {
super(props);
this.state = {
history: [
{
squares: Array(9).fill(null) }],
stepNumber: 0,
xIsNext: true };
}
handleClick(i) {
const history = this.state.history.slice(0, this.state.stepNumber + 1);
const current = history[history.length - 1];
const squares = current.squares.slice();
if (calculateWinner(squares) || squares[i]) {
return;
}
squares[i] = this.state.xIsNext ? "X" : "O";
this.setState({
history: history.concat([
{
squares: squares }]),
stepNumber: history.length,
xIsNext: !this.state.xIsNext });
}
jumpTo(step) {
this.setState({
stepNumber: step,
xIsNext: step % 2 === 0 });
}
render() {
const history = this.state.history;
const current = history[this.state.stepNumber];
const winner = calculateWinner(current.squares);
const moves = history.map((step, move) => {
const desc = move ?
'Go to move #' + move :
'Go to game start';
return (
React.createElement("li", { key: move },
React.createElement("button", { onClick: () => this.jumpTo(move) }, desc)));
});
let status;
if (winner) {
status = "Winner: " + winner;
} else {
status = "Next player: " + (this.state.xIsNext ? "X" : "O");
}
return (
React.createElement("div", { className: "game" },
React.createElement("div", { className: "game-board" },
React.createElement(Board, {
squares: current.squares,
onClick: i => this.handleClick(i) })),
React.createElement("div", { className: "game-info" },
React.createElement("div", { id: "status" }, status),
React.createElement("ol", null, moves))));
}}
// ========================================
ReactDOM.render(React.createElement(Game, null), document.getElementById("root"));
function calculateWinner(squares) {
const lines = [
[0, 1, 2],
[3, 4, 5],
[6, 7, 8],
[0, 3, 6],
[1, 4, 7],
[2, 5, 8],
[0, 4, 8],
[2, 4, 6]];
for (let i = 0; i < lines.length; i++) {if (window.CP.shouldStopExecution(0)) break;
const [a, b, c] = lines[i];
if (squares[a] && squares[a] === squares[b] && squares[a] === squares[c]) {
return squares[a];
}
}window.CP.exitedLoop(0);
return null;
}
//# sourceURL=pen.js
</script>
</body></html>
It's a tic tac toe game from this codepen link https://codepen.io/sophiebits/pen/qNOZOP (I've modified it a little bit).
When I used the inspect utility of Chrome to select one square, and looked at the computed css box model, it says the content of the square is 32px. But in the css portion of the html file, I clearly set the width of the square to be 34px, shouldn't the computed css box model have content width of 34px?
I know that the default value of box-sizing is content-box, so the width property should contain neither border nor padding. That's why I have this question.
Buttons (and some other items) use a box-sizing property "border-box" instead of "content-box"; this explains the different behavior.
Documentation for reference: https://developer.mozilla.org/en-US/docs/Web/CSS/box-sizing
You can change this in css using:
.square{
box-sizing: content-box; /* border-box */
}
See this very simple JSFiddle: https://jsfiddle.net/ue5f164h/
<div class="box"></div>
.box {
background: blue;
border: 5px solid red;
width: 20px;
height: 20px;
}
For me, the width is correct!
You should give a smaller reproducible example in your question so that it's easier for us to debug what's gone wrong.
But as the other answer points out, the problem is that some elements have a different default box-sizing value.
Basically the way box-sizing works is that, it determines if element width should include border or not.
For example -> If you have a div with width of 300px
By using box-sizing: border-box like so
div {
width: 300px;
height: 100px;
border: 10px solid blue;
box-sizing: border-box;
}
Then the width of this div element will always be 300px -> 20px of border and 280px of content -> by adding padding or changing border size you are changing the size of the content
If you use box-sizing: content-box like so:
div {
width: 300px;
height: 100px;
border: 10px solid red;
box-sizing: content-box;
}
The width of the div element will now be 320px -> 300px of content + 20px of border width.
So if you want your element to always have width or height of lets say 50px you would use box-sizing: border-box
Iam using react Autosuggest component for city country list. When an input is given the suggested list of cities name with their respective countries is displayed. I am able to change the background color while hovering, but, I want to change when key pressed 'up' or 'down'. Following is the code for reference
Form.js
const getSuggestions = value => {
const inputValue = value.trim().toLowerCase();
const inputLength = inputValue.length;
return inputLength === 0 ? [] : cities.filter(data =>
data.name.toLowerCase().slice(0, inputLength) === inputValue
).slice(0,4);
};
// When suggestion is clicked, Autosuggest needs to populate the input
// based on the clicked suggestion.
const getSuggestionValue = suggestion => suggestion.name
const renderSuggestion = suggestion =>
(
<table className='auto-complete'>
<tbody>
<ArrowTooltip title={suggestion.name + ', ' + countries.getName(suggestion.country,"en")} placement="top">
<tr>
<td style={{ width: '88%' }} dangerouslySetInnerHTML=
{highlight(suggestion.name, suggestion.value)}></td>
<td style={{ width: '12%' }}>{suggestion.country}</td>
</tr>
</ArrowTooltip>
</tbody>
</table>
);
class Form extends React {
constructor() {
super();
this.state = {
value: '',
suggestions: []
};
}
onChange = (event, { newValue, method }) => {
this.setState({
value: newValue,
});
if(method == 'up' || method == 'down') {
// Here the value is triggered when key pressed
console.log('KEY UP/DOWN')
}
};
onSuggestionsFetchRequested = ({ value }) => {
this.setState({
suggestions: getSuggestions(value)
});
};
// Autosuggest will call this function every time you need to clear suggestions.
onSuggestionsClearRequested = () => {
this.setState({
suggestions: []
});
};
onSuggestionSelected = (event, {suggestion}) => {
const isSuggestion = true
this.props.getWeather(suggestion, isSuggestion)
}
render() {
const { value, suggestions} = this.state;
// Autosuggest will pass through all these props to the input.
const inputProps = {
placeholder: 'Search City...',
value,
onChange: this.onChange
};
<Autosuggest
suggestions={suggestions}
onSuggestionsFetchRequested={this.onSuggestionsFetchRequested}
onSuggestionsClearRequested={this.onSuggestionsClearRequested}
getSuggestionValue={getSuggestionValue}
renderSuggestion={renderSuggestion}
inputProps={inputProps}
onSuggestionSelected={this.onSuggestionSelected}
/>
}
}
export default Form
CSS required for suggestions (which is to be styled)
Form.css
.react-autosuggest__suggestions-container {
display: none;
}
.react-autosuggest__suggestions-container--open {
display: block;
perspective: 1000px;
position:absolute;
top: 30px;
width: 180px;
font-family: 'Oxygen';
font-weight: 100;
font-size: 16px;
border-bottom-left-radius: 4px;
border-bottom-right-radius: 4px;
z-index: 2;
}
#media screen and (max-width: 576px) {
.react-autosuggest__suggestions-container--open, .react-autosuggest__input {
display: block;
width: 220px;
}
}
.react-autosuggest__suggestions-container--open li{
transform-origin: top center;
margin-bottom: 2px;
background: #c7ecee;
}
/* Here the background color is changed on hovering each li */
.react-autosuggest__suggestions-container--open li:hover {
background: #95afc0;
transition: 0.1s ease-in-out;
}
As seen above, when the mouse is hovered over each elements background color changes, but, I want to change when key (up or down) is pressed. Any suggestions or changes??
Just add theme prop. Something like this:
<Autosuggest
suggestions={suggestions}
onSuggestionsFetchRequested={
this.onSuggestionsFetchRequested
}
onSuggestionsClearRequested={
this.onSuggestionsClearRequested
}
getSuggestionValue={this.getSuggestionValue}
renderSuggestion={this.renderSuggestion}
inputProps={inputProps}
focusInputOnSuggestionClick={true}
theme={{
container: {
display: "block",
width: "100%",
position: "absolute",
borderTop: "none",
borderBottom: "1px solid #aaa",
borderLeft: "1px solid #aaa",
borderRight: "1px solid #aaa",
backgroundColor: "#fff",
fontFamily: "Helvetica, sans-serif",
fontWeight: 300,
fontSize: "16px",
borderBottomLeftRadius: "4px",
borderBottomRightRadius: "4px",
zIndex: 2,
paddingLeft: 0,
paddingRight: 0,
},
input: {
marginBottom: 0,
display: "block",
width: "100%",
height: "34px",
padding: "6px 12px",
fontSize: "14px",
lineHeight: "1.4285",
color: "#555",
backgroundColor: "#fff",
backgroundImage: "none",
borderRadius: "4px",
border: "1px solid #ccc",
},
suggestionHighlighted: {
backgroundColor: "lightgrey", //magic line
cursor:'pointer'
},
}}
/>
I'm using the microsoft/BotFramework-WebChat, but I'm having problems getting it to scroll properly.
Often when the bot responds the user is forced to manually scroll to the bottom of the chat log.
I can't find any documentation about hooks that would let me call an API to scroll it.
Is there a way to get it so that the chat window scrolls automatically?
HTML:
<div id="bot-button" style="display:none" >
<p id="need-help" class="triangle-isosceles">Hey! Need any help?</p>
<div id="bot-open" token="temptoken">
<span>
<img id="avatar" src="/img/avatar.png"/>
<i id="message-count">2</i>
</span>
</div>
<div id="bot-close"><img src="/img/close.png" height="20px"/>Close</div>
<div id="webchat" role="main"></div>
</div>
<script src="https://cdn.botframework.com/botframework-webchat/master/webchat.js"></script>
<script src="/js/chat.js"></script>
JavaScript:
(async function () {
// In this demo, we are using Direct Line token from MockBot.
// To talk to your bot, you should use the token exchanged using your Direct Line secret.
// You should never put the Direct Line secret in the browser or client app.
// https://learn.microsoft.com/en-us/azure/bot-service/rest-api/bot-framework-rest-direct-line-3-0-authentication
var bearer_token = document.getElementById("bot-open").getAttribute("token");
const res = await fetch('https://directline.botframework.com/v3/directline/tokens/generate', {
method: 'POST',
headers: {
"Authorization": "Bearer " + bearer_token
}
});
const {
token
} = await res.json();
// We are using a customized store to add hooks to connect event
const store = window.WebChat.createStore({}, ({
dispatch
}) => next => action => {
if (action.type === 'DIRECT_LINE/CONNECT_FULFILLED') {
// When we receive DIRECT_LINE/CONNECT_FULFILLED action, we will send an event activity using WEB_CHAT/SEND_EVENT
dispatch({
type: 'WEB_CHAT/SEND_EVENT',
payload: {
name: 'webchat/join',
value: {
language: window.navigator.language
}
}
});
}
return next(action);
});
const styleOptions = {
bubbleBackground: 'rgba(0, 0, 255, .1)',
bubbleFromUserBackground: 'rgba(0, 255, 0, .1)',
hideUploadButton: true,
botAvatarInitials: 'DA',
};
window.WebChat.renderWebChat({
directLine: window.WebChat.createDirectLine({
token
}),
userID: guid(),
store,
styleOptions
}, document.getElementById('webchat'));
sizeBotChat();
document.querySelector('#webchat > *').focus();
})().catch(err => console.error(err));
function sizeBotChat() {
let bot_container = document.getElementById("bot-button");
if (isMobileDevice()) {
bot_container.style.width = "100%";
bot_container.style.bottom = "0px";
bot_container.style.right = "0px";
let max_height = screen.height - 50;
document.getElementById("webchat").style.maxHeight = max_height + "px";
console.log(screen.height);
} else {
bot_container.style.width = "400px";
bot_container.style.right = "50px";
document.getElementById("webchat").style.maxHeight = "400px";
}
}
CSS (loaded by javascript inserting a link into the head element):
.triangle-isosceles {
position: relative;
padding: 15px;
color: black;
background: white;
border-radius: 10px;
}
/* creates triangle */
.triangle-isosceles:after {
content: "";
display: block;
/* reduce the damage in FF3.0 */
position: absolute;
bottom: -15px;
right: 30px;
width: 0;
border-width: 15px 15px 0;
border-style: solid;
border-color: white transparent;
}
#avatar {
height: 50px;
}
#need-help {
display: none;
}
/* based on badge progress-bar-danger from bootstrap */
#message-count {
display: inline-block;
min-width: 10px;
padding: 3px 7px 3px 7px;
font-size: 12px;
font-weight: 700;
line-height: 1;
color: white;
text-align: center;
white-space: nowrap;
vertical-align: middle;
border-radius: 10px;
background-color: #d9534f;
position: relative;
top: -20px;
right: 20px;
}
#bot-button {
position: fixed;
bottom: 50px;
right: 0px;
width: 100%;
}
#bot-open {
height: 50px;
width: 100%;
text-align: right;
}
#bot-close {
background-color: blue;
background-image: url("https://localhost/img/avatar.png");
background-repeat: no-repeat;
color: white;
height: 22px;
display: none;
height: 50px;
padding: 15px 15px 0 0;
text-align: right;
vertical-align: middle;
}
/* hide chat on load */
#webchat {
display: none;
max-height: 400px;
overflow: scroll;
}
#webchat div.row.message {
margin: 0;
}
The developers designed WebChat to scroll the conversation to the bottom if the user hasn't scrolled up. If the user has scrolled up, there should be a 'New Message' button that appears in the bottom right corner of the chat when the bot sends a new message.
You can modify this behavior by using a custom middleware - which it looks like you already are - and scrolling the last element in the conversation into view when the user receives a message from the bot. See the code snippet below.
const store = window.WebChat.createStore(
{},
({ dispatch }) => next => action => {
if (action.type === 'DIRECT_LINE/INCOMING_ACTIVITY') {
document.querySelector('ul[role="list"]').lastChild.scrollIntoView({behavior: 'smooth', block: 'start'});
}
...
return next(action);
}
);
Hope this helps!