Skip to content

Commit

Permalink
Merge pull request #379 from Automattic/add/sites-dropdown
Browse files Browse the repository at this point in the history
Framework: Create new <SitesDropdown> component.
  • Loading branch information
mtias committed Dec 14, 2015
2 parents 78c7197 + 888e7f6 commit cb701c2
Show file tree
Hide file tree
Showing 11 changed files with 319 additions and 45 deletions.
1 change: 1 addition & 0 deletions assets/stylesheets/_components.scss
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@
@import 'components/site-icon/style';
@import 'components/site-selector/style';
@import 'components/site-selector-modal/style';
@import 'components/sites-dropdown/style';
@import 'components/sites-popover/style';
@import 'components/spinner/style';
@import 'components/stat-update-indicator/style';
Expand Down
9 changes: 2 additions & 7 deletions assets/stylesheets/layout/_main.scss
Original file line number Diff line number Diff line change
Expand Up @@ -289,25 +289,20 @@ $sidebar-width-min: 228px;
}

.wpcom-sidebar,
.site-selector,
.wp-secondary .site-selector,
.current-site,
.sidebar-menu {
transform: translateX( 0 );
transition: all 0.15s cubic-bezier(0.075, 0.820, 0.165, 1.000);
}

.site-selector {
opacity: 0;
pointer-events: none;
}

.focus-sites {
.wp-primary {
opacity: 0.2;
pointer-events: none;
}

.site-selector {
.wp-secondary .site-selector {
opacity: 1;
transform: translateX( 272px );
pointer-events: auto;
Expand Down
19 changes: 19 additions & 0 deletions assets/stylesheets/layout/_sidebar.scss
Original file line number Diff line number Diff line change
Expand Up @@ -284,3 +284,22 @@
color: $blue-medium;
}
}

// site selector in the sidebar
.wp-secondary .site-selector {
background: lighten( $gray, 30% );
border-right: 1px solid lighten( $gray, 25% );
position: fixed;
top: 47px;
bottom: 0;
left: -272px;
width: 272px;
overflow: hidden;
z-index: 10;
opacity: 0;
pointer-events: none;

.search {
border-bottom: 1px solid lighten( $gray, 20% );
}
}
65 changes: 46 additions & 19 deletions client/components/site-selector/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,15 +25,21 @@ module.exports = React.createClass( {
showAllSites: React.PropTypes.bool,
indicator: React.PropTypes.bool,
autoFocus: React.PropTypes.bool,
onClose: React.PropTypes.func
onClose: React.PropTypes.func,
selected: React.PropTypes.string,
hideSelected: React.PropTypes.bool
},

getDefaultProps: function() {
return {
showAddNewSite: false,
showAllSites: false,
siteBasePath: false,
indicator: false,
onClose: noop
hideSelected: false,
selected: null,
onClose: noop,
onSiteSelect: noop
};
},

Expand All @@ -51,12 +57,13 @@ module.exports = React.createClass( {
this.setState( { search: terms } );
},

onSiteSelect: function( event ) {
onSiteSelect: function( siteSlug, event ) {
this.closeSelector();
this.props.onSiteSelect( siteSlug );
this.props.onClose( event );

// ignore mouse events as the default page() click event will handle navigation
if ( event.type !== 'mouseup' ) {
if ( this.props.siteBasePath && event.type !== 'mouseup' ) {
page( event.currentTarget.pathname );
}
},
Expand Down Expand Up @@ -106,9 +113,32 @@ module.exports = React.createClass( {
);
},

getSiteBasePath: function( site ) {
var siteBasePath = this.props.siteBasePath,
postsBase = ( site.jetpack || site.single_user_site ) ? '/posts' : '/posts/my';

// Default posts to /posts/my when possible and /posts when not
siteBasePath = siteBasePath.replace( /^\/posts\b(\/my)?/, postsBase );

// Default stats to /stats/slug when on a 3rd level post/page summary
if ( siteBasePath.match( /^\/stats\/(post|page)\// ) ) {
siteBasePath = '/stats';
}

if ( siteBasePath.match( /^\/domains\/manage\// ) ) {
siteBasePath = '/domains/manage';
}

return siteBasePath;
},

isSelected: function( site ) {
return this.props.sites.selected === site.domain || this.props.selected === site.slug;
},

renderSiteElements: function() {
var allSitesPath = this.props.allSitesPath,
sites, postsBase, siteElements;
sites, siteElements;

if ( this.state.search ) {
sites = this.props.sites.search( this.state.search );
Expand All @@ -118,36 +148,33 @@ module.exports = React.createClass( {

// Render sites
siteElements = sites.map( function( site ) {
var siteBasePath = this.props.siteBasePath;
postsBase = ( site.jetpack || site.single_user_site ) ? '/posts' : '/posts/my';

// Default posts to /posts/my when possible and /posts when not
siteBasePath = siteBasePath.replace( /^\/posts\b(\/my)?/, postsBase );
var siteHref;

// Default stats to /stats/slug when on a 3rd level post/page summary
if ( siteBasePath.match( /^\/stats\/(post|page)\// ) ) {
siteBasePath = '/stats';
if ( this.props.siteBasePath ) {
siteHref = this.getSiteBasePath( site ) + '/' + site.slug;
}

if ( siteBasePath.match( /^\/domains\/manage\// ) ) {
siteBasePath = '/domains/manage';
const isSelected = this.isSelected( site );

if ( isSelected && this.props.hideSelected ) {
return;
}

return (
<Site
site={ site }
href={ siteBasePath + '/' + site.slug }
href={ this.props.siteBasePath ? siteHref : null }
key={ 'site-' + site.ID }
indicator={ this.props.indicator }
onSelect={ this.onSiteSelect }
isSelected={ this.props.sites.selected === site.domain }
onSelect={ this.onSiteSelect.bind( this, site.slug ) }
isSelected={ isSelected }
/>
);
}, this );

if ( this.props.showAllSites && ! this.state.search && allSitesPath ) {
// default posts links to /posts/my when possible and /posts when not
postsBase = ( this.props.sites.allSingleSites ) ? '/posts' : '/posts/my';
const postsBase = ( this.props.sites.allSingleSites ) ? '/posts' : '/posts/my';
allSitesPath = allSitesPath.replace( /^\/posts\b(\/my)?/, postsBase );

// There is currently no "all sites" version of the insights page
Expand Down
14 changes: 5 additions & 9 deletions client/components/site-selector/style.scss
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,10 @@
* @component `selector`
*/
.site-selector {
background: lighten( $gray, 30% );
border-right: 1px solid lighten( $gray, 25% );
position: fixed;
top: 47px;
bottom: 0;
left: -272px;
width: 272px;
overflow: hidden;
overflow: visible;
position: static;
pointer-events: auto;
border: none;
z-index: z-index( 'root', '.site-selector' );

&.is-large .search {
Expand Down Expand Up @@ -83,7 +79,7 @@
width: auto;
height: auto;
padding: 8px;
border-bottom: 1px solid lighten( $gray, 20% );
border-bottom: 1px solid lighten( $gray, 30% );

.search__input[type="search"] {
position: relative;
Expand Down
18 changes: 18 additions & 0 deletions client/components/sites-dropdown/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
Sites Dropdown
==============

Renders a dropdown component for selecting a site. This is the canonical site picker component for using whenever you need to offer users a site selection flow.

It support searching if you have many sites, handles sites with empty titles, sites with redirects, etc.

#### How to use:

```js
import SitesDropdown 'components/sites-dropdown';

render() {
return (
<SitesDropdown />
);
}
```
25 changes: 25 additions & 0 deletions client/components/sites-dropdown/docs/example.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/**
* External dependencies
*/
import React from 'react';

/**
* Internal dependencies
*/
import SitesDropdown from 'components/sites-dropdown';

export default React.createClass( {

displayName: 'SitesDropdownExample',

render: function() {
return (
<div className="design-assets__group">
<h2>
<a href="/devdocs/design/sites-dropdown">SitesDropdown</a>
</h2>
<SitesDropdown />
</div>
);
}
} );
92 changes: 92 additions & 0 deletions client/components/sites-dropdown/index.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
/**
* External dependencies
*/
import React from 'react';
import classNames from 'classnames';
import noop from 'lodash/utility/noop';

/**
* Internal dependencies
*/
import Site from 'my-sites/site';
import SiteSelector from 'components/site-selector';
import sitesList from 'lib/sites-list';
import Gridicon from 'components/gridicon';

const sites = sitesList();

export default React.createClass( {

displayName: 'SitesDropdown',

mixins: [ React.addons.PureRenderMixin ],

propTypes: {
selected: React.PropTypes.oneOfType( [
React.PropTypes.number,
React.PropTypes.string
] ),
showAllSites: React.PropTypes.bool,
indicator: React.PropTypes.bool,
autoFocus: React.PropTypes.bool,
onClose: React.PropTypes.func,
onSiteSelect: React.PropTypes.func
},

getDefaultProps() {
return {
showAllSites: false,
indicator: false,
onClose: noop,
onSiteSelect: noop
};
},

getInitialState() {
return {
search: '',
selected: this.props.selected || sites.getPrimary().slug
};
},

getSelectedSite() {
return sites.getSite( this.state.selected );
},

selectSite( siteSlug ) {
this.props.onSiteSelect( siteSlug );
this.setState( {
selected: siteSlug,
open: false
} );
},

render() {
return (
<div className={ classNames( 'sites-dropdown', { 'is-open': this.state.open } ) }>
<div className="sites-dropdown__wrapper">
<div
className="sites-dropdown__selected"
onClick={ () => this.setState( { open: ! this.state.open } ) }
>
<Site
site={ this.getSelectedSite() }
indicator={ false }
/>
<Gridicon icon="chevron-down" />
</div>
{ this.state.open &&
<SiteSelector
sites={ sites }
autoFocus={ true }
onClose={ this.props.onClose }
onSiteSelect={ this.selectSite }
selected={ this.state.selected }
hideSelected={ true }
/>
}
</div>
</div>
);
}
} );
Loading

0 comments on commit cb701c2

Please sign in to comment.