From 75980bdbc38c24b5d7b48e93b030ee62b7934e5c Mon Sep 17 00:00:00 2001 From: Matthew Kevins Date: Wed, 4 Dec 2019 22:13:26 +1000 Subject: [PATCH] [RNMobile] Gallery - Native gallery component draft (#18176) * 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 --- .../media-placeholder/index.native.js | 19 ++- .../media-placeholder/styles.native.scss | 14 +- .../src/gallery/gallery-styles.native.scss | 3 + .../src/gallery/gallery.native.js | 132 ++++++++++++++++++ packages/block-library/src/index.native.js | 2 + 5 files changed, 163 insertions(+), 7 deletions(-) create mode 100644 packages/block-library/src/gallery/gallery-styles.native.scss create mode 100644 packages/block-library/src/gallery/gallery.native.js diff --git a/packages/block-editor/src/components/media-placeholder/index.native.js b/packages/block-editor/src/components/media-placeholder/index.native.js index 3680401362a4cb..2e375ec1684833 100644 --- a/packages/block-editor/src/components/media-placeholder/index.native.js +++ b/packages/block-editor/src/components/media-placeholder/index.native.js @@ -2,6 +2,7 @@ * External dependencies */ import { View, Text, TouchableWithoutFeedback } from 'react-native'; +import { uniqBy } from 'lodash'; /** * WordPress dependencies @@ -22,6 +23,7 @@ import styles from './styles.scss'; function MediaPlaceholder( props ) { const { + addToGallery, allowedTypes = [], labels = {}, icon, @@ -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 ); @@ -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 ) { @@ -85,9 +93,9 @@ function MediaPlaceholder( props ) { return ( ); } @@ -97,13 +105,14 @@ function MediaPlaceholder( props ) { return null; } + const appenderStyle = getStylesFromColorScheme( styles.appender, styles.appenderDark ); const emptyStateContainerStyle = getStylesFromColorScheme( styles.emptyStateContainer, styles.emptyStateContainerDark ); return ( { return ( @@ -122,7 +131,7 @@ function MediaPlaceholder( props ) { { getMediaOptions() } { renderContent() } diff --git a/packages/block-editor/src/components/media-placeholder/styles.native.scss b/packages/block-editor/src/components/media-placeholder/styles.native.scss index 03c7c73b2edfc9..c5bf2a49ed13c4 100644 --- a/packages/block-editor/src/components/media-placeholder/styles.native.scss +++ b/packages/block-editor/src/components/media-placeholder/styles.native.scss @@ -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; +} diff --git a/packages/block-library/src/gallery/gallery-styles.native.scss b/packages/block-library/src/gallery/gallery-styles.native.scss new file mode 100644 index 00000000000000..3d50b4f47f3a4e --- /dev/null +++ b/packages/block-library/src/gallery/gallery-styles.native.scss @@ -0,0 +1,3 @@ +.galleryTilesContainerSelected { + margin-bottom: 16px; +} diff --git a/packages/block-library/src/gallery/gallery.native.js b/packages/block-library/src/gallery/gallery.native.js new file mode 100644 index 00000000000000..b4f0cb8ba881a1 --- /dev/null +++ b/packages/block-library/src/gallery/gallery.native.js @@ -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 ( + + + { 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 ( + onSetImageAttributes( index, attrs ) } + caption={ img.caption } + aria-label={ ariaLabel } + /> + ); + } ) } + + { mediaPlaceholder } + + 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 + /> + + ); +}; + +export default Gallery; diff --git a/packages/block-library/src/index.native.js b/packages/block-library/src/index.native.js index 22e534fb2669e5..8fa2cf849bb66b 100644 --- a/packages/block-library/src/index.native.js +++ b/packages/block-library/src/index.native.js @@ -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 );