Skip to content

Commit

Permalink
[RNMobile] Gallery - Native gallery component draft (#18176)
Browse files Browse the repository at this point in the history
* Add native gallery

* Add native gallery block behind DEV flag

* Refactor gallery to accept props more directly

* Pass isBlockSelected prop to gallery-image

* Pass isCropped prop to gallery-image

* Limit displayed columns on mobile for gallery

* Fix lint errors

* Use renamed tiles spacing prop in native gallery component

* [RNMobile] Gallery - Add append logic to MediaPlaceholder (#18262)

* Add append logic to MediaPlaceholder for gallery

* Fix lint errors

* Add margin-bottom to tiles in native gallery

* Limit displayed gallery columns to 4 for viewports < large

* Fix lint

* Add darkmode styles for MediaPlaceholder appender

* Use child-first approach for gallery image UI components

* Limit displayed columns in gallery to 4 on native

* Add block-level caption to native gallery

* Fix scss imports for jest

* Fix lint

* Use "narrow" instead of "mobile" semantics for viewport flag
  • Loading branch information
mkevins authored Dec 4, 2019
1 parent 7c813ed commit 75980bd
Show file tree
Hide file tree
Showing 5 changed files with 163 additions and 7 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
* External dependencies
*/
import { View, Text, TouchableWithoutFeedback } from 'react-native';
import { uniqBy } from 'lodash';

/**
* WordPress dependencies
Expand All @@ -22,6 +23,7 @@ import styles from './styles.scss';

function MediaPlaceholder( props ) {
const {
addToGallery,
allowedTypes = [],
labels = {},
icon,
Expand All @@ -30,8 +32,13 @@ function MediaPlaceholder( props ) {
disableMediaButtons,
getStylesFromColorScheme,
multiple,
value = [],
} = props;

const setMedia = multiple && addToGallery ?
( selected ) => onSelect( uniqBy( [ ...value, ...selected ], 'id' ) ) :
onSelect;

const isOneType = allowedTypes.length === 1;
const isImage = isOneType && allowedTypes.includes( MEDIA_TYPE_IMAGE );
const isVideo = isOneType && allowedTypes.includes( MEDIA_TYPE_VIDEO );
Expand Down Expand Up @@ -65,6 +72,7 @@ function MediaPlaceholder( props ) {
}

const emptyStateTitleStyle = getStylesFromColorScheme( styles.emptyStateTitle, styles.emptyStateTitleDark );
const addMediaButtonStyle = getStylesFromColorScheme( styles.addMediaButton, styles.addMediaButtonDark );

const renderContent = () => {
if ( isAppender === undefined || ! isAppender ) {
Expand All @@ -85,9 +93,9 @@ function MediaPlaceholder( props ) {
return (
<Dashicon
icon="plus-alt"
style={ styles.addBlockButton }
color={ styles.addBlockButton.color }
size={ styles.addBlockButton.size }
style={ addMediaButtonStyle }
color={ addMediaButtonStyle.color }
size={ addMediaButtonStyle.size }
/>
);
}
Expand All @@ -97,13 +105,14 @@ function MediaPlaceholder( props ) {
return null;
}

const appenderStyle = getStylesFromColorScheme( styles.appender, styles.appenderDark );
const emptyStateContainerStyle = getStylesFromColorScheme( styles.emptyStateContainer, styles.emptyStateContainerDark );

return (
<View style={ { flex: 1 } }>
<MediaUpload
allowedTypes={ allowedTypes }
onSelect={ onSelect }
onSelect={ setMedia }
multiple={ multiple }
render={ ( { open, getMediaOptions } ) => {
return (
Expand All @@ -122,7 +131,7 @@ function MediaPlaceholder( props ) {
<View
style={ [
emptyStateContainerStyle,
isAppender && styles.isAppender,
isAppender && appenderStyle,
] }>
{ getMediaOptions() }
{ renderContent() }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,17 +46,27 @@
fill: $gray-dark;
}

.isAppender {
.appender {
height: auto;
background-color: $white;
border: $border-width solid $light-gray-500;
border-radius: 4px;
}

.addBlockButton {
.appenderDark {
background-color: $background-dark-secondary;
border: $border-width solid $gray-70;
}

.addMediaButton {
color: $white;
background-color: $dark-gray-500;
border-radius: $icon-button-size-small / 2;
overflow: hidden;
size: $icon-button-size-small;
}

.addMediaButtonDark {
color: $background-dark-secondary;
background-color: $gray-40;
}
3 changes: 3 additions & 0 deletions packages/block-library/src/gallery/gallery-styles.native.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.galleryTilesContainerSelected {
margin-bottom: 16px;
}
132 changes: 132 additions & 0 deletions packages/block-library/src/gallery/gallery.native.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
/**
* External dependencies
*/
import { View } from 'react-native';
import { isEmpty } from 'lodash';

/**
* Internal dependencies
*/
import GalleryImage from './gallery-image';
import { defaultColumnsNumber } from './shared';
import styles from './gallery-styles.scss';
import Tiles from './tiles';

/**
* WordPress dependencies
*/
import { __, sprintf } from '@wordpress/i18n';
import { Caption } from '@wordpress/block-editor';
import { useState } from '@wordpress/element';

const TILE_SPACING = 15;

// we must limit displayed columns since readable content max-width is 580px
const MAX_DISPLAYED_COLUMNS = 4;
const MAX_DISPLAYED_COLUMNS_NARROW = 2;

export const Gallery = ( props ) => {
const [ isCaptionSelected, setIsCaptionSelected ] = useState( false );

const {
clientId,
selectedImage,
mediaPlaceholder,
onBlur,
onMoveBackward,
onMoveForward,
onRemoveImage,
onSelectImage,
onSetImageAttributes,
onFocusGalleryCaption,
attributes,
isSelected,
isNarrow,
onFocus,
} = props;

const {
columns = defaultColumnsNumber( attributes ),
imageCrop,
images,
} = attributes;

// limit displayed columns when isNarrow is true (i.e. when viewport width is
// less than "small", where small = 600)
const displayedColumns = isNarrow ?
Math.min( columns, MAX_DISPLAYED_COLUMNS_NARROW ) :
Math.min( columns, MAX_DISPLAYED_COLUMNS );

const selectImage = ( index ) => {
return () => {
if ( isCaptionSelected ) {
setIsCaptionSelected( false );
}
// we need to fully invoke the curried function here
onSelectImage( index )();
};
};

const focusGalleryCaption = () => {
if ( ! isCaptionSelected ) {
setIsCaptionSelected( true );
}
onFocusGalleryCaption();
};

return (
<View>
<Tiles
columns={ displayedColumns }
spacing={ TILE_SPACING }
style={ isSelected ? styles.galleryTilesContainerSelected : undefined }
>
{ images.map( ( img, index ) => {
/* translators: %1$d is the order number of the image, %2$d is the total number of images. */
const ariaLabel = sprintf( __( 'image %1$d of %2$d in gallery' ), ( index + 1 ), images.length );

return (
<GalleryImage
key={ img.id || img.url }
url={ img.url }
alt={ img.alt }
id={ img.id }
isCropped={ imageCrop }
isFirstItem={ index === 0 }
isLastItem={ ( index + 1 ) === images.length }
isSelected={ isSelected && selectedImage === index }
isBlockSelected={ isSelected }
onMoveBackward={ onMoveBackward( index ) }
onMoveForward={ onMoveForward( index ) }
onRemove={ onRemoveImage( index ) }
onSelect={ selectImage( index ) }
onSelectBlock={ onFocus }
setAttributes={ ( attrs ) => onSetImageAttributes( index, attrs ) }
caption={ img.caption }
aria-label={ ariaLabel }
/>
);
} ) }
</Tiles>
{ mediaPlaceholder }
<Caption
clientId={ clientId }
isSelected={ isCaptionSelected }
accessible={ true }
accessibilityLabelCreator={ ( caption ) =>
isEmpty( caption ) ?
/* translators: accessibility text. Empty gallery caption. */
( 'Gallery caption. Empty' ) :
sprintf(
/* translators: accessibility text. %s: gallery caption. */
__( 'Gallery caption. %s' ),
caption )
}
onFocus={ focusGalleryCaption }
onBlur={ onBlur } // always assign onBlur as props
/>
</View>
);
};

export default Gallery;
2 changes: 2 additions & 0 deletions packages/block-library/src/index.native.js
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,8 @@ export const registerCoreBlocks = () => {
// eslint-disable-next-line no-undef
!! __DEV__ ? group : null,
spacer,
// eslint-disable-next-line no-undef
!! __DEV__ ? gallery : null,
].forEach( registerBlock );

setDefaultBlockName( paragraph.name );
Expand Down

0 comments on commit 75980bd

Please sign in to comment.