Skip to content

Commit

Permalink
Grid View Modal (#4153)
Browse files Browse the repository at this point in the history
  • Loading branch information
swissspidy committed Jan 24, 2020
1 parent 4ee8a5e commit 3b165c4
Show file tree
Hide file tree
Showing 11 changed files with 482 additions and 98 deletions.
2 changes: 2 additions & 0 deletions assets/src/edit-story/app/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import {
*/
import theme, { GlobalStyle } from '../theme';
import { GlobalStyle as CropMoveableGlobalStyle } from '../components/movable/cropStyle';
import { GlobalStyle as ModalGlobalStyle } from '../components/modal';
import { useHistory, HistoryProvider } from './history';
import { useAPI, APIProvider } from './api';
import { useConfig, ConfigProvider } from './config';
Expand All @@ -37,6 +38,7 @@ function App( { config } ) {
<FontProvider>
<GlobalStyle />
<CropMoveableGlobalStyle />
<ModalGlobalStyle />
<Layout />
<Popover.Slot />
</FontProvider>
Expand Down
151 changes: 62 additions & 89 deletions assets/src/edit-story/components/canvas/carousel/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,10 @@ import { __, sprintf } from '@wordpress/i18n';
* Internal dependencies
*/
import { useStory } from '../../../app';
import { LeftArrow, RightArrow, GridView } from '../../button';
import DropZone from '../../dropzone';
import { LeftArrow, RightArrow, GridView as GridViewButton } from '../../button';
import Modal from '../../modal';
import GridView from '../gridview';
import DraggablePage from '../draggablePage';

// @todo: Make responsive. Blocked on the header reimplementation and
// responsive "page" size.
Expand Down Expand Up @@ -48,32 +50,22 @@ const List = styled( Area )`
overflow-x: ${ ( { hasHorizontalOverflow } ) => hasHorizontalOverflow ? 'scroll' : 'hidden' };
`;

const Page = styled.button`
padding: 0;
margin: 0 5px;
height: ${ PAGE_HEIGHT }px;
width: ${ PAGE_WIDTH }px;
background-color: ${ ( { isActive, theme } ) => isActive ? theme.colors.fg.v1 : theme.colors.mg.v1 };
flex: none;
outline: 2px solid ${ ( { isActive, theme } ) => isActive ? theme.colors.selection : theme.colors.bg.v1 };
&:focus, &:hover {
outline: 2px solid ${ ( { theme } ) => theme.colors.selection };
}
`;

const GridViewButton = styled( GridView )`
const StyledGridViewButton = styled( GridViewButton )`
position: absolute;
bottom: 24px;
`;

function Carousel() {
const { state: { pages, currentPageIndex, currentPageId }, actions: { setCurrentPage, arrangePage } } = useStory();
const { state: { pages, currentPageIndex, currentPageId }, actions: { setCurrentPage } } = useStory();
const [ hasHorizontalOverflow, setHasHorizontalOverflow ] = useState( false );
const [ scrollPercentage, setScrollPercentage ] = useState( 0 );
const [ isGridViewOpen, setIsGridViewOpen ] = useState( false );
const listRef = useRef();
const pageRefs = useRef( [] );

const openModal = useCallback( () => setIsGridViewOpen( true ), [ setIsGridViewOpen ] );
const closeModal = useCallback( () => setIsGridViewOpen( false ), [ setIsGridViewOpen ] );

useLayoutEffect( () => {
const observer = new ResizeObserver( ( entries ) => {
for ( const entry of entries ) {
Expand Down Expand Up @@ -134,88 +126,69 @@ function Carousel() {
} );
}, [ listRef ] );

const getArrangeIndex = ( sourceIndex, dstIndex, position ) => {
// If the dropped element is before the dropzone index then we have to deduct
// that from the index to make up for the "lost" element in the row.
const indexAdjustment = sourceIndex < dstIndex ? -1 : 0;
if ( 'left' === position.x ) {
return dstIndex + indexAdjustment;
}
return dstIndex + 1 + indexAdjustment;
};

const onDragStart = useCallback( ( index ) => ( evt ) => {
const pageData = {
type: 'page',
index,
};
evt.dataTransfer.setData( 'text', JSON.stringify( pageData ) );
}, [] );

const onDrop = ( evt, { position, pageIndex } ) => {
const droppedEl = JSON.parse( evt.dataTransfer.getData( 'text' ) );
if ( ! droppedEl || 'page' !== droppedEl.type ) {
return;
}
const arrangedIndex = getArrangeIndex( droppedEl.index, pageIndex, position );
// Do nothing if the index didn't change.
if ( droppedEl.index !== arrangedIndex ) {
const pageId = pages[ droppedEl.index ].id;
arrangePage( { pageId, position: arrangedIndex } );
setCurrentPage( { pageId } );
}
};

const isAtBeginningOfList = 0 === scrollPercentage;
const isAtEndOfList = 1 === scrollPercentage;

return (
<Wrapper>
<Area area="left-navigation">
<LeftArrow
isHidden={ ! hasHorizontalOverflow || isAtBeginningOfList }
onClick={ () => scrollBy( -( 2 * PAGE_WIDTH ) ) }
width="24"
height="24"
aria-label={ __( 'Scroll Left', 'amp' ) }
/>
</Area>
<List area="carousel" ref={ listRef } hasHorizontalOverflow={ hasHorizontalOverflow }>
{ pages.map( ( page, index ) => {
const isCurrentPage = index === currentPageIndex;
return (
<DropZone key={ index } onDrop={ onDrop } pageIndex={ index } >
<Page
<>
<Wrapper>
<Area area="left-navigation">
<LeftArrow
isHidden={ ! hasHorizontalOverflow || isAtBeginningOfList }
onClick={ () => scrollBy( -( 2 * PAGE_WIDTH ) ) }
width="24"
height="24"
aria-label={ __( 'Scroll Left', 'amp' ) }
/>
</Area>
<List area="carousel" ref={ listRef } hasHorizontalOverflow={ hasHorizontalOverflow }>
{ pages.map( ( page, index ) => {
const isCurrentPage = index === currentPageIndex;

return (
<DraggablePage
key={ index }
draggable="true"
onClick={ handleClickPage( page ) }
onDragStart={ onDragStart( index ) }
ariaLabel={ isCurrentPage ?
sprintf( __( 'Page %s (current page)', 'amp' ), index + 1 ) :
sprintf( __( 'Go to page %s', 'amp' ), index + 1 )
}
isActive={ isCurrentPage }
pageIndex={ index }
ref={ ( el ) => {
pageRefs.current[ page.id ] = el;
} }
aria-label={ isCurrentPage ? sprintf( __( 'Page %s (current page)', 'amp' ), index + 1 ) : sprintf( __( 'Go to page %s', 'amp' ), index + 1 ) }
width={ PAGE_WIDTH }
height={ PAGE_HEIGHT }
/>
</DropZone>
);
} ) }
</List>
<Area area="right-navigation">
<RightArrow
isHidden={ ! hasHorizontalOverflow || isAtEndOfList }
onClick={ () => scrollBy( ( 2 * PAGE_WIDTH ) ) }
width="24"
height="24"
aria-label={ __( 'Scroll Right', 'amp' ) }
/>
<GridViewButton
isDisabled
width="24"
height="24"
aria-label={ __( 'Grid View', 'amp' ) }
/>
</Area>
</Wrapper>
);
} ) }
</List>
<Area area="right-navigation">
<RightArrow
isHidden={ ! hasHorizontalOverflow || isAtEndOfList }
onClick={ () => scrollBy( ( 2 * PAGE_WIDTH ) ) }
width="24"
height="24"
aria-label={ __( 'Scroll Right', 'amp' ) }
/>
<StyledGridViewButton
width="24"
height="24"
onClick={ openModal }
aria-label={ __( 'Grid View', 'amp' ) }
/>
</Area>
</Wrapper>
<Modal
isOpen={ isGridViewOpen }
onRequestClose={ closeModal }
contentLabel={ __( 'Grid View', 'amp' ) }
closeButtonLabel={ __( 'Back', 'amp' ) }
>
<GridView />
</Modal>
</>
);
}

Expand Down
87 changes: 87 additions & 0 deletions assets/src/edit-story/components/canvas/draggablePage.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
/**
* External dependencies
*/
import styled from 'styled-components';

/**
* WordPress dependencies
*/
import { forwardRef, useCallback } from '@wordpress/element';

/**
* Internal dependencies
*/
import DropZone from '../dropzone';
import { useStory } from '../../app/story';

const Page = styled.button`
padding: 0;
margin: 0;
border: none;
outline: 2px solid ${ ( { isActive, theme } ) => isActive ? theme.colors.selection : theme.colors.bg.v1 };
&:focus, &:hover {
outline: 2px solid ${ ( { theme } ) => theme.colors.selection };
}
height: ${ ( { height } ) => height }px;
width: ${ ( { width } ) => width }px;
background-color: ${ ( { theme } ) => theme.colors.mg.v1 };
flex: none;
transition: width .2s ease, height .2s ease;
`;

// Disable reason: forwardRef render functions do not support propTypes
//eslint-disable-next-line react/prop-types
function DraggablePageWithRef( { pageIndex, onClick, isActive, ariaLabel, width, height, dragIndicatorOffset }, ref ) {
const { state: { pages }, actions: { setCurrentPage, arrangePage } } = useStory();

const getArrangeIndex = ( sourceIndex, dstIndex, position ) => {
// If the dropped element is before the dropzone index then we have to deduct
// that from the index to make up for the "lost" element in the row.
const indexAdjustment = sourceIndex < dstIndex ? -1 : 0;
if ( 'left' === position.x ) {
return dstIndex + indexAdjustment;
}
return dstIndex + 1 + indexAdjustment;
};

const onDragStart = useCallback( ( evt ) => {
const pageData = {
type: 'page',
index: pageIndex,
};
evt.dataTransfer.setData( 'text', JSON.stringify( pageData ) );
}, [ pageIndex ] );

const onDrop = ( evt, { position } ) => {
const droppedEl = JSON.parse( evt.dataTransfer.getData( 'text' ) );
if ( ! droppedEl || 'page' !== droppedEl.type ) {
return;
}
const arrangedIndex = getArrangeIndex( droppedEl.index, pageIndex, position );
// Do nothing if the index didn't change.
if ( droppedEl.index !== arrangedIndex ) {
const pageId = pages[ droppedEl.index ].id;
arrangePage( { pageId, position: arrangedIndex } );
setCurrentPage( { pageId } );
}
};

return (
<DropZone onDrop={ onDrop } pageIndex={ pageIndex } dragIndicatorOffset={ dragIndicatorOffset }>
<Page
draggable="true"
onClick={ onClick }
onDragStart={ onDragStart }
isActive={ isActive }
aria-label={ ariaLabel }
width={ width }
height={ height }
ref={ ref }
/>
</DropZone>
);
}

const DraggablePage = forwardRef( DraggablePageWithRef );

export default DraggablePage;
Loading

0 comments on commit 3b165c4

Please sign in to comment.