Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Framework: Create new <SitesDropdown> component. #379

Merged
merged 10 commits into from
Dec 14, 2015
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;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you add this to z-index map?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, good call.

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: '',
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Post-merge comment -- do we need this.state.search in this component?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll remove it in #1483.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

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