0
0
mirror of https://github.com/naturalcrit/homebrewery.git synced 2026-01-08 05:22:40 +00:00
Change dropdowns to stay open when clicked
This commit is contained in:
Trevor Buckner
2023-07-07 20:46:05 -04:00
committed by GitHub
6 changed files with 125 additions and 134 deletions

View File

@@ -15,7 +15,7 @@ module.exports = {
rules : { rules : {
/** Errors **/ /** Errors **/
'camelcase' : ['error', { properties: 'never' }], 'camelcase' : ['error', { properties: 'never' }],
'func-style' : ['error', 'expression', { allowArrowFunctions: true }], //'func-style' : ['error', 'expression', { allowArrowFunctions: true }],
'no-array-constructor' : 'error', 'no-array-constructor' : 'error',
'no-iterator' : 'error', 'no-iterator' : 'error',
'no-nested-ternary' : 'error', 'no-nested-ternary' : 'error',

View File

@@ -185,6 +185,12 @@ const BrewRenderer = createClass({
}, 100); }, 100);
}, },
emitClick : function(){
// console.log('iFrame clicked');
if(!window || !document) return;
document.dispatchEvent(new MouseEvent('click'));
},
render : function(){ render : function(){
//render in iFrame so broken code doesn't crash the site. //render in iFrame so broken code doesn't crash the site.
//Also render dummy page while iframe is mounting. //Also render dummy page while iframe is mounting.
@@ -203,7 +209,9 @@ const BrewRenderer = createClass({
<Frame id='BrewRenderer' initialContent={this.state.initialContent} <Frame id='BrewRenderer' initialContent={this.state.initialContent}
style={{ width: '100%', height: '100%', visibility: this.state.visibility }} style={{ width: '100%', height: '100%', visibility: this.state.visibility }}
contentDidMount={this.frameDidMount}> contentDidMount={this.frameDidMount}
onClick={()=>{this.emitClick();}}
>
<div className={'brewRenderer'} <div className={'brewRenderer'}
onScroll={this.handleScroll} onScroll={this.handleScroll}
style={{ height: this.state.height }}> style={{ height: this.state.height }}>

View File

@@ -81,20 +81,70 @@
color : pink; color : pink;
} }
} }
.recent.navItem { .recent.navDropdownContainer {
position : relative; position : relative;
.dropdown { .navDropdown .navItem {
position : absolute;
z-index : 10000;
top : 28px;
left : 0;
overflow : hidden auto; overflow : hidden auto;
width : 100%;
max-height : ~"calc(100vh - 28px)"; max-height : ~"calc(100vh - 28px)";
scrollbar-color : #666 #333; scrollbar-color : #666 #333;
scrollbar-width : thin; scrollbar-width : thin;
h4 {
font-size : 0.8em;
#backgroundColorsHover;
.animate(background-color);
position : relative;
display : block;
overflow : clip;
box-sizing : border-box;
padding : 8px 5px 13px;
text-decoration : none;
color : white;
border-top : 1px solid #888;
background-color : #333;
.clear {
position : absolute;
top : 50%;
right : 0;
display : none;
width : 20px;
height : 100%;
transform : translateY(-50%);
opacity : 70%;
border-radius : 3px;
background-color : #333;
&:hover {
opacity : 100%;
}
i {
font-size : 10px;
width : 100%;
height : 100%;
margin : 0;
text-align : center;
}
}
&:hover {
background-color : @blue;
.clear {
display : grid;
place-content : center;
}
}
.title {
display : inline-block;
overflow : hidden;
width : 100%;
white-space : nowrap;
text-overflow : ellipsis;
}
.time {
font-size : 0.7em;
position : absolute;
right : 2px;
bottom : 2px;
color : #888;
}
&.header {
display : block; display : block;
box-sizing : border-box; box-sizing : border-box;
padding : 5px 0; padding : 5px 0;
@@ -109,62 +159,6 @@
background-color : darken(@purple, 30%); background-color : darken(@purple, 30%);
} }
} }
.item {
#backgroundColorsHover;
.animate(background-color);
position : relative;
display : block;
overflow : clip;
box-sizing : border-box;
padding : 8px 5px 13px;
text-decoration : none;
color : white;
border-top : 1px solid #888;
background-color : #333;
.clear {
position : absolute;
top : 50%;
right : 0;
display : none;
width : 20px;
height : 100%;
transform : translateY(-50%);
opacity : 70%;
border-radius : 3px;
background-color : #333;
&:hover {
opacity : 100%;
}
i {
font-size : 10px;
width : 100%;
height : 100%;
margin : 0;
text-align : center;
}
}
&:hover {
background-color : @blue;
.clear {
display : grid;
place-content : center;
}
}
.title {
display : inline-block;
overflow : hidden;
width : 100%;
white-space : nowrap;
text-overflow : ellipsis;
}
.time {
font-size : 0.7em;
position : absolute;
right : 2px;
bottom : 2px;
color : #888;
}
}
} }
} }
.metadata.navItem { .metadata.navItem {

View File

@@ -121,6 +121,7 @@ const RecentItems = createClass({
removeItem : function(url, evt){ removeItem : function(url, evt){
evt.preventDefault(); evt.preventDefault();
evt.stopPropagation();
let edited = JSON.parse(localStorage.getItem(EDIT_KEY) || '[]'); let edited = JSON.parse(localStorage.getItem(EDIT_KEY) || '[]');
let viewed = JSON.parse(localStorage.getItem(VIEW_KEY) || '[]'); let viewed = JSON.parse(localStorage.getItem(VIEW_KEY) || '[]');
@@ -139,11 +140,11 @@ const RecentItems = createClass({
}, },
renderDropdown : function(){ renderDropdown : function(){
if(!this.state.showDropdown) return null; // if(!this.state.showDropdown) return null;
const makeItems = (brews)=>{ const makeItems = (brews)=>{
return _.map(brews, (brew, i)=>{ return _.map(brews, (brew, i)=>{
return <a href={brew.url} className='item' key={`${brew.id}-${i}`} target='_blank' rel='noopener noreferrer' title={brew.title || '[ no title ]'}> return <a className='navItem' href={brew.url} key={`${brew.id}-${i}`} target='_blank' rel='noopener noreferrer' title={brew.title || '[ no title ]'}>
<span className='title'>{brew.title || '[ no title ]'}</span> <span className='title'>{brew.title || '[ no title ]'}</span>
<span className='time'>{Moment(brew.ts).fromNow()}</span> <span className='time'>{Moment(brew.ts).fromNow()}</span>
<div className='clear' title='Remove from Recents' onClick={(e)=>{this.removeItem(`${brew.url}`, e);}}><i className='fas fa-times'></i></div> <div className='clear' title='Remove from Recents' onClick={(e)=>{this.removeItem(`${brew.url}`, e);}}><i className='fas fa-times'></i></div>
@@ -151,25 +152,25 @@ const RecentItems = createClass({
}); });
}; };
return <div className='dropdown'> return <>
{(this.props.showEdit && this.props.showView) ? {(this.props.showEdit && this.props.showView) ?
<h4>edited</h4> : null } <Nav.item className='header'>edited</Nav.item> : null }
{this.props.showEdit ? {this.props.showEdit ?
makeItems(this.state.edit) : null } makeItems(this.state.edit) : null }
{(this.props.showEdit && this.props.showView) ? {(this.props.showEdit && this.props.showView) ?
<h4>viewed</h4> : null } <Nav.item className='header'>viewed</Nav.item> : null }
{this.props.showView ? {this.props.showView ?
makeItems(this.state.view) : null } makeItems(this.state.view) : null }
</div>; </>;
}, },
render : function(){ render : function(){
return <Nav.item icon='fas fa-history' color='grey' className='recent' return <Nav.dropdown className='recent'>
onMouseEnter={()=>this.handleDropdown(true)} <Nav.item icon='fas fa-history' color='grey' >
onMouseLeave={()=>this.handleDropdown(false)}> {this.props.text}
{this.props.text} </Nav.item>
{this.renderDropdown()} {this.renderDropdown()}
</Nav.item>; </Nav.dropdown>;
} }
}); });

View File

@@ -1,5 +1,6 @@
require('./nav.less'); require('./nav.less');
const React = require('react'); const React = require('react');
const { useState, useRef, useEffect } = React;
const createClass = require('create-react-class'); const createClass = require('create-react-class');
const _ = require('lodash'); const _ = require('lodash');
const cx = require('classnames'); const cx = require('classnames');
@@ -71,64 +72,49 @@ const Nav = {
} }
}), }),
dropdown : createClass({ dropdown : function dropdown(props) {
displayName : 'Nav.dropdown', props = Object.assign({}, props, {
getDefaultProps : function() { trigger : 'hover click'
return { });
trigger : 'hover'
};
},
getInitialState : function() {
return {
showDropdown : false
};
},
componentDidMount : function() {
if(this.props.trigger == 'click')
document.addEventListener('click', this.handleClickOutside);
},
componentWillUnmount : function() {
if(this.props.trigger == 'click')
document.removeEventListener('click', this.handleClickOutside);
},
handleClickOutside : function(e){
// Close dropdown when clicked outside
if(this.refs.dropdown && !this.refs.dropdown.contains(e.target)) {
this.handleDropdown(false);
}
},
handleDropdown : function(show){
this.setState({
showDropdown : show
});
},
renderDropdown : function(dropdownChildren){
if(!this.state.showDropdown) return null;
return ( const myRef = useRef(null);
<div className='navDropdown'> const [showDropdown, setShowDropdown] = useState(false);
{dropdownChildren}
</div> useEffect(()=>{
); document.addEventListener('click', handleClickOutside);
}, return ()=>{
render : function () { document.removeEventListener('click', handleClickOutside);
const dropdownChildren = React.Children.map(this.props.children, (child, i)=>{ };
// Ignore the first child }, []);
if(i < 1) return;
return child; function handleClickOutside(e) {
}); // Close dropdown when clicked outside
return ( if(!myRef.current?.contains(e.target)) {
<div className={`navDropdownContainer ${this.props.className}`} handleDropdown(false);
ref='dropdown' }
onMouseEnter={this.props.trigger == 'hover' ? ()=>{this.handleDropdown(true);} : undefined}
onClick= {this.props.trigger == 'click' ? ()=>{this.handleDropdown(true);} : undefined}
onMouseLeave={this.props.trigger == 'hover' ? ()=>{this.handleDropdown(false);} : undefined}>
{this.props.children[0] || this.props.children /*children is not an array when only one child*/}
{this.renderDropdown(dropdownChildren)}
</div>
);
} }
})
function handleDropdown(show) {
setShowDropdown(show ?? !showDropdown);
}
const dropdownChildren = React.Children.map(props.children, (child, i)=>{
if(i < 1) return;
return child;
});
return (
<div className={`navDropdownContainer ${props.className}`}
ref={myRef}
onMouseEnter = { props.trigger.includes('hover') ? ()=>handleDropdown(true) : undefined }
onMouseLeave = { props.trigger.includes('hover') ? ()=>handleDropdown(false) : undefined }
onClick = { props.trigger.includes('click') ? ()=>handleDropdown(true) : undefined }
>
{props.children[0] || props.children /*children is not an array when only one child*/}
{showDropdown && <div className='navDropdown'>{dropdownChildren}</div>}
</div>
);
}
}; };

View File

@@ -79,6 +79,8 @@ nav{
left : 0px; left : 0px;
z-index : 10000; z-index : 10000;
width : 100%; width : 100%;
overflow : hidden auto;
max-height : calc(100vh - 28px);
.navItem{ .navItem{
animation-name: glideDropDown; animation-name: glideDropDown;
animation-duration: 0.4s; animation-duration: 0.4s;