WIP: Shadow DOM

This commit is contained in:
Manuel Bouza
2019-02-19 15:32:53 +01:00
parent dd4fa996e8
commit a9007b825e
21 changed files with 251 additions and 220 deletions

View File

@@ -1,93 +1,91 @@
#moco-bx-container {
input {
border-radius: 0;
input {
border-radius: 0;
}
.form-group {
width: 100%;
margin: 1rem 0;
label {
display: block;
font-weight: bold;
margin-bottom: 0.25rem;
}
.form-group {
input, textarea {
padding: 0.25rem 0.5rem;
background-color: white;
border-color: #cccccc;
width: 100%;
margin: 1rem 0;
}
label {
display: block;
font-weight: bold;
margin-bottom: 0.25rem;
}
text-muted {
color: #eee;
}
&.has-error {
input, textarea {
padding: 0.25rem 0.5rem;
background-color: white;
border-color: #cccccc;
width: 100%;
border-color: #FB3A2F;
}
}
text-muted {
color: #eee;
}
.form-error {
color: #FB3A2F;
}
&.has-error {
input, textarea {
border-color: #FB3A2F;
}
}
.input-group {
position: relative;
display: table;
border-collapse: separate;
.form-error {
color: #FB3A2F;
}
.input-group {
input {
position: relative;
display: table;
border-collapse: separate;
input {
position: relative;
display: table-cell;
}
.input-group-addon {
padding: 0.25rem 0.5rem;
font-weight: normal;
color: #555555;
text-align: center;
background-color: #eeeeee;
border: 1px solid #cccccc;
border-left: none;
display: table-cell;
width: 1%;
}
}
}
input[name="hours"] {
width: 33%;
}
textarea[name="description"] {
resize: none;
width: 100%;
}
button {
display: inline-block;
padding: 6px 12px;
margin-bottom: 0;
font-weight: normal;
text-align: center;
white-space: nowrap;
color: white;
background-image: none;
background-color: #7dc332;
border-color: #7dc332;
cursor: pointer;
&:hover:not(:disabled) {
background-color: #639a28;
border-color: #639a28;
display: table-cell;
}
&:disabled {
opacity: 0.65;
cursor: default;
.input-group-addon {
padding: 0.25rem 0.5rem;
font-weight: normal;
color: #555555;
text-align: center;
background-color: #eeeeee;
border: 1px solid #cccccc;
border-left: none;
display: table-cell;
width: 1%;
}
}
}
input[name="hours"] {
width: 33%;
}
textarea[name="description"] {
resize: none;
width: 100%;
}
button {
display: inline-block;
padding: 6px 12px;
margin-bottom: 0;
font-weight: normal;
text-align: center;
white-space: nowrap;
color: white;
background-image: none;
background-color: #7dc332;
border-color: #7dc332;
cursor: pointer;
&:hover:not(:disabled) {
background-color: #639a28;
border-color: #639a28;
}
&:disabled {
opacity: 0.65;
cursor: default;
}
}

45
src/css/_reset.scss Normal file
View File

@@ -0,0 +1,45 @@
#moco-bx-bubble, #moco-bx-container {
html, body, div, span, applet, object, iframe,
h1, h2, h3, h4, h5, h6, p, blockquote, pre,
a, abbr, acronym, address, big, cite, code,
del, dfn, em, img, ins, kbd, q, s, samp,
small, strike, strong, sub, sup, tt, var,
b, u, i, center,
dl, dt, dd, ol, ul, li,
fieldset, form, label, legend,
table, caption, tbody, tfoot, thead, tr, th, td,
article, aside, canvas, details, embed,
figure, figcaption, footer, header, hgroup,
menu, nav, output, ruby, section, summary,
time, mark, audio, video {
margin: 0;
padding: 0;
border: 0;
font-size: 100%;
font: inherit;
vertical-align: baseline;
}
/* HTML5 display-role reset for older browsers */
article, aside, details, figcaption, figure,
footer, header, hgroup, menu, nav, section {
display: block;
}
body {
line-height: 1;
}
ol, ul {
list-style: none;
}
blockquote, q {
quotes: none;
}
blockquote:before, blockquote:after,
q:before, q:after {
content: '';
content: none;
}
table {
border-collapse: collapse;
border-spacing: 0;
}
}

View File

@@ -1,11 +1,11 @@
#moco-bx-bubble, #moco-bx-container {
@keyframes spinner {
@keyframes moco-bx-spinner {
to {
transform: rotate(360deg);
}
}
.spinner {
.moco-bx-spinner {
display: inline-block;
width: 2rem;
height: 2rem;
@@ -13,6 +13,6 @@
border: 2px solid #999;
border-right-color: transparent;
border-radius: 50%;
animation: spinner .75s linear infinite;
animation: moco-bx-spinner .75s linear infinite;
}
}

31
src/css/content.scss Normal file
View File

@@ -0,0 +1,31 @@
@import "mixins";
@import "form";
@import "spinner";
.moco-bx-bubble {
img {
width: 30px;
height: 30px;
}
}
.moco-bx-modal {
position: fixed; /* Stay in place */
z-index: 2000; /* Sit on top */
padding-top: 100px; /* Location of the box */
left: 0;
top: 0;
width: 100%; /* Full width */
height: 100%; /* Full height */
overflow: auto; /* Enable scroll if needed */
background-color: rgb(0, 0, 0); /* Fallback color */
background-color: rgba(0, 0, 0, 0.4); /* Black w/ opacity */
.moco-bx-modal-content {
background-color: white;
width: 600px;
padding: 40px;
margin: 0 auto;
}
}

View File

@@ -1,48 +0,0 @@
@import "mixins";
@import "form";
@import "spinner";
#moco-bx-bubble {
position: fixed;
bottom: 40px;
left: 50%;
margin-left: -30px;
z-index: 1000;
height: 60px;
width: 60px;
background-color: white;
border-radius: 50%;
box-shadow: -1px -1px 15px 4px rgba(0, 0, 0, 0.05),
2px 2px 15px 4px rgba(0, 0, 0, 0.05);
padding: 5px;
display: flex;
flex-direction: column;
justify-content: space-around;
align-items: center;
cursor: pointer;
}
#moco-bx-container {
.moco-bx-modal {
position: fixed; /* Stay in place */
z-index: 2000; /* Sit on top */
padding-top: 100px; /* Location of the box */
left: 0;
top: 0;
width: 100%; /* Full width */
height: 100%; /* Full height */
overflow: auto; /* Enable scroll if needed */
background-color: rgb(0, 0, 0); /* Fallback color */
background-color: rgba(0, 0, 0, 0.4); /* Black w/ opacity */
}
.moco-bx-modal-content {
background-color: white;
width: 600px;
padding: 40px;
margin: 0 auto;
}
}

0
src/css/options.scss Normal file
View File

22
src/css/styles.css Normal file
View File

@@ -0,0 +1,22 @@
#moco-bx-root {
position: fixed;
bottom: 40px;
left: 50%;
margin-left: -30px;
z-index: 1000;
height: 60px;
width: 60px;
background-color: white;
border-radius: 50%;
box-shadow: -1px -1px 15px 4px rgba(0, 0, 0, 0.05),
2px 2px 15px 4px rgba(0, 0, 0, 0.05);
padding: 5px;
display: flex;
flex-direction: column;
justify-content: space-around;
align-items: center;
cursor: pointer;
}

View File

@@ -1,7 +1,7 @@
import React, { Component } from "react"
import PropTypes from "prop-types"
import ApiClient from "api/Client"
import Modal, { Content } from "components/Modal"
import Modal from "components/Modal"
import InvalidConfigurationError from "components/InvalidConfigurationError"
import Form from "components/Form"
import Spinner from "components/Spinner"
@@ -160,6 +160,7 @@ class Bubble extends Component {
// EVENT HANDLERS -----------------------------------------------------------
handleKeyDown = event => {
event.stopPropagation()
if (event.keyCode === 27) {
this.close()
}
@@ -223,19 +224,18 @@ class Bubble extends Component {
}
return (
<>
<div className="moco-bx-bubble">
<img
onClick={this.open}
src={chrome.extension.getURL(logoUrl)}
width="50%"
/>
{this.bookedHours > 0 && <span className="booked-hours"><small>{this.bookedHours}h</small></span>}
{this.isOpen && (
<Modal>
<Content>{this.renderContent()}</Content>
{this.renderContent()}
</Modal>
)}
</>
</div>
)
}
}

View File

@@ -1,5 +1,4 @@
import React, { Component } from "react"
import { createPortal } from "react-dom"
import PropTypes from "prop-types"
class Modal extends Component {
@@ -7,35 +6,17 @@ class Modal extends Component {
children: PropTypes.node.isRequired
}
constructor(props) {
super(props)
this.el = document.createElement("div")
this.el.setAttribute("class", "moco-bx-modal")
}
componentDidMount() {
const modalRoot = document.getElementById("moco-bx-container")
modalRoot.appendChild(this.el)
}
componentWillUnmount() {
const modalRoot = document.getElementById("moco-bx-container")
modalRoot.removeChild(this.el)
}
// RENDER -------------------------------------------------------------------
render() {
return createPortal(this.props.children, this.el)
return (
<div className="moco-bx-modal">
<div className="moco-bx-modal-content">
{this.props.children}
</div>
</div>
)
}
}
export function Content({ children }) {
return <div className="moco-bx-modal-content">{children}</div>
}
Content.propTypes = {
children: PropTypes.node
}
export default Modal

View File

@@ -12,7 +12,6 @@ import {
flatMap,
pathEq
} from "lodash/fp"
import { trace } from "utils"
const customTheme = theme => ({
...theme,

View File

@@ -1,9 +1,7 @@
import React from 'react'
const Spinner = () => (
<div className="spinner" role="status">
<span className="sr-only">Loading...</span>
</div>
<div className="moco-bx-spinner" role="status" />
)
export default Spinner

View File

@@ -1,11 +1,11 @@
import { createElement } from "react"
import React from "react"
import ReactDOM from "react-dom"
import ShadowDOM from "react-shadow"
import Bubble from "./components/Bubble"
import services from "remoteServices"
import { parseServices, createMatcher, createEnhancer } from "utils/urlMatcher"
import { createMatcher, createEnhancer } from "utils/urlMatcher"
import remoteServices from "./remoteServices"
import { pipe } from 'lodash/fp'
import "../css/main.scss"
import "../css/content.scss"
const matcher = createMatcher(remoteServices)
const serviceEnhancer = createEnhancer(window.document)
@@ -32,35 +32,27 @@ const mountBubble = (settings) => {
return
}
if (!document.getElementById("moco-bx-container")) {
const domContainer = document.createElement("div")
domContainer.setAttribute("id", "moco-bx-container")
document.body.appendChild(domContainer)
}
if (!document.getElementById("moco-bx-bubble")) {
const domBubble = document.createElement("div")
domBubble.setAttribute("id", "moco-bx-bubble")
document.body.appendChild(domBubble)
if (!document.getElementById("moco-bx-root")) {
const domRoot = document.createElement("div")
domRoot.setAttribute("id", "moco-bx-root")
document.body.appendChild(domRoot)
}
ReactDOM.render(
createElement(Bubble, { service, settings }),
document.getElementById("moco-bx-bubble")
<ShadowDOM include={[chrome.extension.getURL('content.css')]}>
<div>
<Bubble service={service} settings={settings} />
</div>
</ShadowDOM>,
document.getElementById("moco-bx-root")
)
}
const unmountBubble = () => {
const domBubble = document.getElementById("moco-bx-bubble")
const domContainer = document.getElementById("moco-bx-container")
const domRoot = document.getElementById("moco-bx-root")
if (domBubble) {
ReactDOM.unmountComponentAtNode(domBubble)
domBubble.remove()
}
if (domContainer) {
ReactDOM.unmountComponentAtNode(domContainer)
domContainer.remove()
if (domRoot) {
ReactDOM.unmountComponentAtNode(domRoot)
domRoot.remove()
}
}

View File

@@ -1,7 +1,7 @@
import { createElement } from "react"
import React from "react"
import ReactDOM from "react-dom"
import Setup from "./components/Setup"
import "../css/main.scss"
import "../css/options.scss"
const domContainer = document.querySelector("#moco-bx-container")
ReactDOM.render(createElement(Setup), domContainer)
const domContainer = document.querySelector("#moco-bx-root")
ReactDOM.render(<Setup />, domContainer)

View File

@@ -1,6 +0,0 @@
import { createElement } from 'react'
import ReactDOM from 'react-dom'
import Form from './components/Form'
const domContainer = document.querySelector('#moco-bx-container')
ReactDOM.render(createElement(Form, {inline: false}), domContainer)

View File

@@ -25,6 +25,12 @@ export default {
description: (document, service, { org, repo, id }) =>
`${org}/${repo}/${id} - ${document
.querySelector(".gh-header-title")
.textContent.trim()}`
.textContent.trim()}`,
},
"trello": {
name: "trello",
urlPattern: "https://trello.com/c/:id/:title",
description: (document, service, { title }) => title.split('-').slice(1).join(' ')
}
}

View File

@@ -28,7 +28,8 @@
"content_scripts": [
{
"matches": ["<all_urls>"],
"js": ["content.js"]
"js": ["content.js"],
"css": ["styles.css"]
}
],
"browser_action": {
@@ -36,7 +37,7 @@
"default_title": "MOCO Time Tracking",
"default_popup": "popup.html"
},
"web_accessible_resources": ["src/images/*"],
"web_accessible_resources": ["src/images/*", "content.css"],
"commands": {
"_execute_browser_action": {
"suggested_key": {

View File

@@ -4,6 +4,6 @@
<meta charset="utf-8" />
</head>
<body>
<div id="moco-bx-container"></div>
<div id="moco-bx-root"></div>
</body>
</html>

View File

@@ -1,9 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
</head>
<body>
<div id="moco-bx-container"></div>
</body>
</html>