mirror of
https://github.com/Foundry376/Mailspring.git
synced 2025-03-01 10:33:14 +08:00
feat(theme-picker): Add visual theme picker to menu
Summary: Adds a new visual theme picker to the menu that allows users to select different themes based on color palettes and then change their themes live. Test Plan: Test included. Reviewers: evan, bengotow Reviewed By: evan, bengotow Differential Revision: https://phab.nylas.com/D2669
This commit is contained in:
parent
66c84383ff
commit
c67e2a24ea
22 changed files with 543 additions and 44 deletions
|
@ -132,42 +132,6 @@ class AppearanceModeOption extends React.Component
|
|||
<div>{label}</div>
|
||||
</div>
|
||||
|
||||
class ThemeSelector extends React.Component
|
||||
constructor: (@props) ->
|
||||
@_themeManager = NylasEnv.themes
|
||||
@state = @_getState()
|
||||
|
||||
componentDidMount: =>
|
||||
@disposable = @_themeManager.onDidChangeActiveThemes =>
|
||||
@setState @_getState()
|
||||
|
||||
componentWillUnmount: ->
|
||||
@disposable.dispose()
|
||||
|
||||
_getState: =>
|
||||
themes: @_themeManager.getLoadedThemes()
|
||||
activeTheme: @_themeManager.getActiveTheme().name
|
||||
|
||||
_setActiveTheme: (theme) =>
|
||||
@setState activeTheme: theme
|
||||
@_themeManager.setActiveTheme theme
|
||||
|
||||
_onChangeTheme: (event) =>
|
||||
value = event.target.value
|
||||
if value is 'install'
|
||||
NylasEnv.commands.dispatch document.body, 'application:install-package'
|
||||
else
|
||||
@_setActiveTheme(value)
|
||||
|
||||
render: =>
|
||||
<div className="item">
|
||||
<span>Select theme: </span>
|
||||
<select value={@state.activeTheme} onChange={@_onChangeTheme}>
|
||||
{@state.themes.map (theme) ->
|
||||
<option key={theme.name} value={theme.name}>{theme.displayName}</option>}
|
||||
<option value="install">Install a theme...</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
class WorkspaceSection extends React.Component
|
||||
@displayName: 'WorkspaceSection'
|
||||
|
@ -202,8 +166,6 @@ class WorkspaceSection extends React.Component
|
|||
keyPath="core.workspace.interfaceZoom"
|
||||
config={@props.config} />
|
||||
|
||||
<ThemeSelector />
|
||||
|
||||
<h2>Layout</h2>
|
||||
|
||||
<AppearanceModeSwitch config={@props.config} />
|
||||
|
|
18
internal_packages/theme-picker/lib/main.js
Normal file
18
internal_packages/theme-picker/lib/main.js
Normal file
|
@ -0,0 +1,18 @@
|
|||
/** @babel */
|
||||
import React from 'react';
|
||||
import Actions from '../../../src/flux/actions'
|
||||
|
||||
import ThemePicker from './theme-picker'
|
||||
|
||||
|
||||
export function activate() {
|
||||
this.disposable = NylasEnv.commands.add("body",
|
||||
"window:launch-theme-picker",
|
||||
() => Actions.openModal(children=<ThemePicker />,
|
||||
height=400,
|
||||
width=250));
|
||||
}
|
||||
|
||||
export function deactivate() {
|
||||
this.disposable.dispose();
|
||||
}
|
102
internal_packages/theme-picker/lib/theme-option.jsx
Normal file
102
internal_packages/theme-picker/lib/theme-option.jsx
Normal file
|
@ -0,0 +1,102 @@
|
|||
import React from 'react';
|
||||
import fs from 'fs-plus';
|
||||
import path from 'path';
|
||||
|
||||
import {EventedIFrame} from 'nylas-component-kit';
|
||||
import LessCompileCache from '../../../src/less-compile-cache'
|
||||
|
||||
|
||||
class ThemeOption extends React.Component {
|
||||
static propTypes = {
|
||||
theme: React.PropTypes.object.isRequired,
|
||||
active: React.PropTypes.bool.isRequired,
|
||||
}
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.lessCache = null;
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this._writeContent();
|
||||
}
|
||||
|
||||
_getImportPaths() {
|
||||
const themes = [this.props.theme];
|
||||
// Pulls the theme package for Light as the base theme
|
||||
for (const theme of NylasEnv.themes.getActiveThemes()) {
|
||||
if (theme.name === NylasEnv.themes.baseThemeName()) {
|
||||
themes.push(theme);
|
||||
}
|
||||
}
|
||||
const themePaths = [];
|
||||
for (const theme of themes) {
|
||||
themePaths.push(theme.getStylesheetsPath());
|
||||
}
|
||||
return themePaths.filter((themePath) => fs.isDirectorySync(themePath));
|
||||
}
|
||||
|
||||
_loadStylesheet(stylesheetPath) {
|
||||
if (path.extname(stylesheetPath) === '.less') {
|
||||
return this._loadLessStylesheet(stylesheetPath);
|
||||
}
|
||||
return fs.readFileSync(stylesheetPath, 'utf8');
|
||||
}
|
||||
|
||||
_loadLessStylesheet(lessStylesheetPath) {
|
||||
const {configDirPath, resourcePath} = NylasEnv.getLoadSettings();
|
||||
if (this.lessCache) {
|
||||
this.lessCache.setImportPaths(this._getImportPaths());
|
||||
} else {
|
||||
const importPaths = this._getImportPaths();
|
||||
this.lessCache = new LessCompileCache({configDirPath, resourcePath, importPaths});
|
||||
}
|
||||
const themeVarPath = path.relative(`${resourcePath}/internal_packages/theme-picker/preview-styles`,
|
||||
this.props.theme.getStylesheetsPath());
|
||||
let varImports = `@import "../../../static/variables/ui-variables";`
|
||||
if (fs.existsSync(`${this.props.theme.getStylesheetsPath()}/ui-variables.less`)) {
|
||||
varImports += `@import "${themeVarPath}/ui-variables";`
|
||||
}
|
||||
if (fs.existsSync(`${this.props.theme.getStylesheetsPath()}/theme-colors.less`)) {
|
||||
varImports += `@import "${themeVarPath}/theme-colors";`
|
||||
}
|
||||
const less = fs.readFileSync(lessStylesheetPath, 'utf8');
|
||||
return this.lessCache.cssForFile(lessStylesheetPath, [varImports, less].join('\n'));
|
||||
}
|
||||
|
||||
_writeContent() {
|
||||
const domNode = React.findDOMNode(this);
|
||||
const doc = domNode.contentDocument;
|
||||
if (!doc) return;
|
||||
|
||||
const {resourcePath} = NylasEnv.getLoadSettings();
|
||||
|
||||
const html = `<!DOCTYPE html>
|
||||
<style>${this._loadStylesheet(`${resourcePath}/internal_packages/theme-picker/preview-styles/theme-option.less`)}</style>
|
||||
<body>
|
||||
<div class="theme-option active-${this.props.active}">
|
||||
<div class="theme-name ">${this.props.theme.displayName}</div>
|
||||
<div class="swatches" style="display:flex;flex-direction:row;">
|
||||
<div class="swatch font-color"></div>
|
||||
<div class="swatch active-color"></div>
|
||||
<div class="swatch toolbar-color"></div>
|
||||
</div>
|
||||
<div class="divider-black"></div>
|
||||
<div class="divider-white"></div>
|
||||
<div class="strip"></div>
|
||||
</div>
|
||||
</body>`
|
||||
|
||||
doc.open();
|
||||
doc.write(html);
|
||||
doc.close();
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<EventedIFrame ref="iframe" className={`theme-preview-${this.props.theme.name}`} frameBorder="0" width="105px" height="65px" flex="1" style={{pointerEvents: "none"}} />
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default ThemeOption;
|
90
internal_packages/theme-picker/lib/theme-picker.jsx
Normal file
90
internal_packages/theme-picker/lib/theme-picker.jsx
Normal file
|
@ -0,0 +1,90 @@
|
|||
import React from 'react';
|
||||
import Actions from '../../../src/flux/actions'
|
||||
|
||||
import {Flexbox, RetinaImg} from 'nylas-component-kit';
|
||||
import ThemeOption from './theme-option';
|
||||
|
||||
|
||||
class ThemePicker extends React.Component {
|
||||
static displayName = 'ThemePicker';
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this._themeManager = NylasEnv.themes;
|
||||
this.state = this._getState();
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.disposable = this._themeManager.onDidChangeActiveThemes(() => {
|
||||
this.setState(this._getState());
|
||||
});
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.disposable.dispose();
|
||||
}
|
||||
|
||||
_getState() {
|
||||
return {
|
||||
themes: this._themeManager.getLoadedThemes(),
|
||||
activeTheme: this._themeManager.getActiveTheme().name,
|
||||
}
|
||||
}
|
||||
|
||||
_setActiveTheme(theme) {
|
||||
const prevActiveTheme = this.state.activeTheme;
|
||||
this.setState({activeTheme: theme});
|
||||
this._themeManager.setActiveTheme(theme);
|
||||
this._rewriteIFrame(prevActiveTheme, theme);
|
||||
}
|
||||
|
||||
_rewriteIFrame(prevActiveTheme, activeTheme) {
|
||||
const prevActiveThemeDoc = document.querySelector(`.theme-preview-${prevActiveTheme}`).contentDocument;
|
||||
const prevActiveElement = prevActiveThemeDoc.querySelector(".theme-option.active-true");
|
||||
prevActiveElement.className = "theme-option active-false";
|
||||
const activeThemeDoc = document.querySelector(`.theme-preview-${activeTheme}`).contentDocument;
|
||||
const activeElement = activeThemeDoc.querySelector(".theme-option.active-false");
|
||||
activeElement.className = "theme-option active-true";
|
||||
}
|
||||
|
||||
_renderThemeOptions() {
|
||||
const themeOptions = this.state.themes.map((theme) =>
|
||||
<div
|
||||
className="clickable-theme-option"
|
||||
onMouseDown={() => this._setActiveTheme(theme.name)}
|
||||
style={{cursor: "pointer", width: "115px", margin: "2px"}}>
|
||||
<ThemeOption
|
||||
key={theme.name}
|
||||
theme={theme}
|
||||
active={this.state.activeTheme === theme.name} />
|
||||
</div>
|
||||
)
|
||||
return themeOptions;
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div style={{textAlign: "center", cursor: "default"}}>
|
||||
<Flexbox direction="column">
|
||||
<RetinaImg
|
||||
style={{width: "14", height: "14", margin: "8px"}}
|
||||
name="picker-close.png"
|
||||
mode={RetinaImg.Mode.ContentDark}
|
||||
onMouseDown={() => Actions.closeModal()} />
|
||||
<h4 style={{color: "#313435"}}>Themes</h4>
|
||||
<div style={{color: "rgba(35, 31, 32, 0.5)"}}>Click any theme to preview.</div>
|
||||
<div style={{margin: "10px 5px 0 5px", height: "300px", overflow: "auto"}}>
|
||||
<Flexbox
|
||||
direction="row"
|
||||
height="auto"
|
||||
style={{alignItems: "flex-start", flexWrap: "wrap"}}>
|
||||
{this._renderThemeOptions()}
|
||||
</Flexbox>
|
||||
</div>
|
||||
</Flexbox>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default ThemePicker;
|
13
internal_packages/theme-picker/package.json
Normal file
13
internal_packages/theme-picker/package.json
Normal file
|
@ -0,0 +1,13 @@
|
|||
{
|
||||
"name": "theme-picker",
|
||||
"version": "0.1.0",
|
||||
"main": "./lib/main",
|
||||
"description": "View different themes and choose them easily",
|
||||
"license": "GPL-3.0",
|
||||
"private": true,
|
||||
"engines": {
|
||||
"nylas": "*"
|
||||
},
|
||||
"dependencies": {
|
||||
}
|
||||
}
|
|
@ -0,0 +1,93 @@
|
|||
html,
|
||||
body {
|
||||
margin: 0;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
}
|
||||
|
||||
.theme-option {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
width: 100px;
|
||||
height: 60px;
|
||||
background-color: @background-secondary;
|
||||
color: @text-color;
|
||||
border-radius: 5px;
|
||||
text-align: center;
|
||||
overflow: hidden;
|
||||
|
||||
&.active-true {
|
||||
border: 1px solid #3187e1;
|
||||
}
|
||||
|
||||
&.active-false {
|
||||
border: 1px solid darken(#f6f6f6, 10%);
|
||||
}
|
||||
|
||||
.theme-name {
|
||||
font-family: @font-family;
|
||||
font-size: 14px;
|
||||
margin-top: 5px;
|
||||
height: 18px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.swatches {
|
||||
padding-left: 27px;
|
||||
padding-right: 27px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
|
||||
.swatch {
|
||||
flex: 1;
|
||||
height: 10px;
|
||||
width: 10px;
|
||||
margin: 4px 2px 4px 2px;
|
||||
border-radius: 2px;
|
||||
border: 1px solid rgba(0, 0, 0, 0.15);
|
||||
background-clip: border-box;
|
||||
background-origin: border-box;
|
||||
|
||||
&.font-color {
|
||||
background-color: @text-color;
|
||||
}
|
||||
|
||||
&.active-color {
|
||||
background-color: @component-active-color;
|
||||
}
|
||||
|
||||
&.toolbar-color {
|
||||
background-color: @toolbar-background-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.divider-black {
|
||||
position: absolute;
|
||||
bottom: 12px;
|
||||
height: 1px;
|
||||
width: 100%;
|
||||
background-color: black;
|
||||
opacity: 0.15;
|
||||
}
|
||||
|
||||
.divider-white {
|
||||
position: absolute;
|
||||
z-index: 10;
|
||||
bottom: 11px;
|
||||
height: 1px;
|
||||
width: 100%;
|
||||
background-color: white;
|
||||
opacity: 0.15;
|
||||
}
|
||||
|
||||
.strip {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
height: 12px;
|
||||
width: 100%;
|
||||
background-color: @panel-background-color;
|
||||
}
|
||||
}
|
24
internal_packages/theme-picker/spec/theme-picker-spec.jsx
Normal file
24
internal_packages/theme-picker/spec/theme-picker-spec.jsx
Normal file
|
@ -0,0 +1,24 @@
|
|||
import React from 'react';
|
||||
const ReactTestUtils = React.addons.TestUtils;
|
||||
|
||||
import ThemePackage from '../../../src/theme-package';
|
||||
import ThemePicker from '../lib/theme-picker';
|
||||
|
||||
const {resourcePath} = NylasEnv.getLoadSettings();
|
||||
const light = new ThemePackage(resourcePath + '/internal_packages/ui-light');
|
||||
const dark = new ThemePackage(resourcePath + '/internal_packages/ui-dark');
|
||||
|
||||
describe('ThemePicker', ()=> {
|
||||
beforeEach(()=> {
|
||||
spyOn(ThemePicker.prototype, '_setActiveTheme').andCallThrough();
|
||||
spyOn(NylasEnv.themes, 'getLoadedThemes').andReturn([light, dark]);
|
||||
spyOn(NylasEnv.themes, 'getActiveTheme').andReturn(light);
|
||||
this.component = ReactTestUtils.renderIntoDocument(<ThemePicker />);
|
||||
});
|
||||
|
||||
it('changes the active theme when a theme is clicked', ()=> {
|
||||
const themeOption = React.findDOMNode(ReactTestUtils.scryRenderedDOMComponentsWithClass(this.component, 'clickable-theme-option')[1]);
|
||||
ReactTestUtils.Simulate.mouseDown(themeOption);
|
||||
expect(ThemePicker.prototype._setActiveTheme).toHaveBeenCalled();
|
||||
});
|
||||
});
|
|
@ -5,6 +5,8 @@
|
|||
{ label: 'About Nylas', command: 'application:about' }
|
||||
{ type: 'separator' }
|
||||
{ label: 'Preferences', command: 'application:open-preferences' }
|
||||
{ label: 'Change Theme...', command: 'window:launch-theme-picker' }
|
||||
{ label: 'Install New Theme...', command: 'application:install-package' }
|
||||
{ type: 'separator' }
|
||||
{ label: 'Add Account...', command: 'application:add-account' }
|
||||
{ label: 'VERSION', enabled: false }
|
||||
|
|
|
@ -32,6 +32,8 @@
|
|||
] }
|
||||
{ type: 'separator' }
|
||||
{ label: 'Preferences', command: 'application:open-preferences' }
|
||||
{ label: 'Change Theme...', command: 'window:launch-theme-picker' }
|
||||
{ label: 'Install New Theme...', command: 'application:install-package' }
|
||||
]
|
||||
}
|
||||
|
||||
|
|
|
@ -61,6 +61,8 @@
|
|||
}
|
||||
{ type: 'separator' }
|
||||
{ label: 'Preferences', command: 'application:open-preferences' }
|
||||
{ label: 'Change Theme...', command: 'window:launch-theme-picker' }
|
||||
{ label: 'Install New Theme...', command: 'application:install-package' }
|
||||
{ type: 'separator' }
|
||||
{ label: 'Print Current Thread', command: 'application:print-thread' }
|
||||
{ type: 'separator' }
|
||||
|
|
|
@ -21,13 +21,17 @@ class Flexbox extends React.Component
|
|||
direction: React.PropTypes.string
|
||||
inline: React.PropTypes.bool
|
||||
style: React.PropTypes.object
|
||||
height: React.PropTypes.string
|
||||
|
||||
@defaultProps:
|
||||
height: '100%'
|
||||
|
||||
render: ->
|
||||
style = _.extend {}, (@props.style || {}),
|
||||
'flexDirection': @props.direction,
|
||||
'position':'relative'
|
||||
'display': 'flex'
|
||||
'height':'100%'
|
||||
'height': @props.height
|
||||
|
||||
if @props.inline is true
|
||||
style.display = 'inline-flex'
|
||||
|
|
98
src/components/modal.jsx
Normal file
98
src/components/modal.jsx
Normal file
|
@ -0,0 +1,98 @@
|
|||
import _ from 'underscore';
|
||||
import React from 'react';
|
||||
import Actions from '../flux/actions';
|
||||
|
||||
|
||||
class Modal extends React.Component {
|
||||
|
||||
static propTypes = {
|
||||
className: React.PropTypes.string,
|
||||
children: React.PropTypes.element,
|
||||
height: React.PropTypes.number,
|
||||
width: React.PropTypes.number,
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
offset: 0,
|
||||
dimensions: {},
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this._focusImportantElement();
|
||||
}
|
||||
|
||||
_focusImportantElement = ()=> {
|
||||
const modalNode = React.findDOMNode(this);
|
||||
|
||||
const focusable = modalNode.querySelectorAll("[tabIndex], input");
|
||||
const matches = _.sortBy(focusable, (node)=> {
|
||||
if (node.tabIndex > 0) {
|
||||
return node.tabIndex;
|
||||
} else if (node.nodeName === "INPUT") {
|
||||
return 1000000
|
||||
}
|
||||
return 1000001
|
||||
})
|
||||
if (matches[0]) {
|
||||
matches[0].focus();
|
||||
}
|
||||
};
|
||||
|
||||
_computeModalStyles = (height, width)=> {
|
||||
const modalStyle = {
|
||||
top: "50%",
|
||||
left: "50%",
|
||||
margin: "-200px 0 0 -125px",
|
||||
height: height,
|
||||
width: width,
|
||||
position: "absolute",
|
||||
backgroundColor: "white",
|
||||
boxShadow: "0 10px 20px rgba(0,0,0,0.19), inset 0 0 1px rgba(0,0,0,0.5)",
|
||||
borderRadius: "5px",
|
||||
};
|
||||
const containerStyle = {
|
||||
height: "100%",
|
||||
width: "100%",
|
||||
zIndex: 1000,
|
||||
position: "absolute",
|
||||
backgroundColor: "transparent",
|
||||
};
|
||||
return {containerStyle, modalStyle};
|
||||
};
|
||||
|
||||
_onBlur = (event)=> {
|
||||
const target = event.nativeEvent.relatedTarget;
|
||||
if (!target || (!React.findDOMNode(this).contains(target))) {
|
||||
Actions.closeModal();
|
||||
}
|
||||
};
|
||||
|
||||
_onKeyDown = (event)=> {
|
||||
if (event.key === "Escape") {
|
||||
Actions.closeModal();
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
const {children, height, width} = this.props;
|
||||
const {containerStyle, modalStyle} = this._computeModalStyles(height, width);
|
||||
|
||||
return (
|
||||
<div
|
||||
style={containerStyle}
|
||||
className="modal-container"
|
||||
onKeyDown={this._onKeyDown}
|
||||
onBlur={this._onBlur}>
|
||||
<div className="modal" style={modalStyle}>
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default Modal;
|
|
@ -56,7 +56,7 @@ class NewsletterSignup extends React.Component
|
|||
"/newsletter-subscription/#{encodeURIComponent(props.emailAddress)}?name=#{encodeURIComponent(props.name)}"
|
||||
|
||||
render: =>
|
||||
<Flexbox direction='row' style={textAlign: 'left', height: 'auto'}>
|
||||
<Flexbox direction='row' height='auto' style={textAlign: 'left'}>
|
||||
<div style={minWidth:15}>
|
||||
{@_renderControl()}
|
||||
</div>
|
||||
|
|
|
@ -518,6 +518,9 @@ class Actions
|
|||
@openPopover: ActionScopeWindow
|
||||
@closePopover: ActionScopeWindow
|
||||
|
||||
@openModal: ActionScopeWindow
|
||||
@closeModal: ActionScopeWindow
|
||||
|
||||
###
|
||||
Public: Set metadata for a specified model and pluginId.
|
||||
|
||||
|
|
68
src/flux/stores/modal-store.jsx
Normal file
68
src/flux/stores/modal-store.jsx
Normal file
|
@ -0,0 +1,68 @@
|
|||
import React from 'react';
|
||||
import NylasStore from 'nylas-store'
|
||||
import Actions from '../actions'
|
||||
import {Modal} from 'nylas-component-kit';
|
||||
|
||||
|
||||
const CONTAINER_ID = "nylas-modal-container";
|
||||
|
||||
function createContainer(id) {
|
||||
const element = document.createElement(id);
|
||||
document.body.appendChild(element);
|
||||
return element;
|
||||
}
|
||||
|
||||
class ModalStore extends NylasStore {
|
||||
|
||||
constructor(containerId = CONTAINER_ID) {
|
||||
super()
|
||||
this.isOpen = false;
|
||||
this.container = createContainer(containerId);
|
||||
React.render(<span />, this.container);
|
||||
|
||||
this.listenTo(Actions.openModal, this.openModal);
|
||||
this.listenTo(Actions.closeModal, this.closeModal);
|
||||
}
|
||||
|
||||
isModalOpen = ()=> {
|
||||
return this.isOpen;
|
||||
};
|
||||
|
||||
renderModal = (child, props, callback)=> {
|
||||
const modal = (
|
||||
<Modal {...props}>{child}</Modal>
|
||||
);
|
||||
|
||||
React.render(modal, this.container, ()=> {
|
||||
this.isOpen = true;
|
||||
this.trigger();
|
||||
callback();
|
||||
});
|
||||
};
|
||||
|
||||
openModal = (component, height, width, callback = ()=> {})=> {
|
||||
const props = {
|
||||
height: height,
|
||||
width: width,
|
||||
};
|
||||
|
||||
if (this.isOpen) {
|
||||
this.closeModal(()=> {
|
||||
this.renderModal(component, props, callback);
|
||||
})
|
||||
} else {
|
||||
this.renderModal(component, props, callback);
|
||||
}
|
||||
};
|
||||
|
||||
closeModal = (callback = ()=>{})=> {
|
||||
React.render(<span/>, this.container, ()=> {
|
||||
this.isOpen = false;
|
||||
this.trigger();
|
||||
callback();
|
||||
});
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
export default new ModalStore();
|
|
@ -16,6 +16,7 @@ class NylasComponentKit
|
|||
@load "Switch", 'switch'
|
||||
@load "Popover", 'popover'
|
||||
@load "FixedPopover", 'fixed-popover'
|
||||
@load "Modal", 'modal'
|
||||
@load "Flexbox", 'flexbox'
|
||||
@load "RetinaImg", 'retina-img'
|
||||
@load "SwipeContainer", 'swipe-container'
|
||||
|
|
|
@ -130,8 +130,8 @@ class NylasExports
|
|||
@require "FocusedContactsStore", 'flux/stores/focused-contacts-store'
|
||||
@require "PreferencesUIStore", 'flux/stores/preferences-ui-store'
|
||||
@require "PopoverStore", 'flux/stores/popover-store'
|
||||
@require "ModalStore", 'flux/stores/modal-store'
|
||||
@require "SearchableComponentStore", 'flux/stores/searchable-component-store'
|
||||
|
||||
@require "MessageBodyProcessor", 'flux/stores/message-body-processor'
|
||||
@require "MailRulesTemplates", 'mail-rules-templates'
|
||||
@require "MailRulesProcessor", 'mail-rules-processor'
|
||||
|
|
19
static/components/modal.less
Normal file
19
static/components/modal.less
Normal file
|
@ -0,0 +1,19 @@
|
|||
@import "ui-variables";
|
||||
|
||||
.nylas-modal-container {
|
||||
position: absolute;
|
||||
z-index: 40;
|
||||
|
||||
.modal {
|
||||
position: absolute;
|
||||
background-color: @background-primary;
|
||||
border-radius: @border-radius-base;
|
||||
box-shadow: 0 0.5px 0 rgba(0, 0, 0, 0.15), 0 -0.5px 0 rgba(0, 0, 0, 0.15), 0.5px 0 0 rgba(0, 0, 0, 0.15), -0.5px 0 0 rgba(0, 0, 0, 0.15), 0 4px 7px rgba(0,0,0,0.15);
|
||||
}
|
||||
}
|
||||
|
||||
body.platform-win32 {
|
||||
.modal {
|
||||
border-radius: 0;
|
||||
}
|
||||
}
|
BIN
static/images/theme-picker/picker-close@1x.png
Normal file
BIN
static/images/theme-picker/picker-close@1x.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 18 KiB |
BIN
static/images/theme-picker/picker-close@2x.png
Normal file
BIN
static/images/theme-picker/picker-close@2x.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 19 KiB |
|
@ -31,4 +31,5 @@
|
|||
@import "components/editable-list";
|
||||
@import "components/outline-view";
|
||||
@import "components/fixed-popover";
|
||||
@import "components/modal";
|
||||
@import "components/date-input";
|
||||
|
|
|
@ -90,7 +90,6 @@
|
|||
@text-color-search-match: #fff000;
|
||||
@text-color-search-current-match: #ff8b1a;
|
||||
|
||||
@font-family-sans-serif: "Nylas-Pro", "Helvetica", sans-serif;
|
||||
@font-family-sans-serif: "Nylas-Pro", "Helvetica", sans-serif;
|
||||
@font-family-serif: Georgia, "Times New Roman", Times, serif;
|
||||
@font-family-monospace: Menlo, Monaco, Consolas, "Courier New", monospace;
|
||||
|
@ -448,8 +447,6 @@ rgba(253,253,253,0.75) 100%);
|
|||
@component-border-radius: 2px;
|
||||
|
||||
|
||||
|
||||
@body-bg: @white;
|
||||
//== Panels and Sidebars
|
||||
@panel-background-color: @gray-lighter;
|
||||
@toolbar-background-color: darken(@white, 17.5%);
|
||||
|
|
Loading…
Reference in a new issue