diff --git a/blocks/library/gallery/block.js b/blocks/library/gallery/block.js
new file mode 100644
index 00000000000000..19712352623a9b
--- /dev/null
+++ b/blocks/library/gallery/block.js
@@ -0,0 +1,205 @@
+/**
+ * External Dependencies
+ */
+import { filter } from 'lodash';
+
+/**
+ * WordPress dependencies
+ */
+import { Component } from '@wordpress/element';
+import { __ } from '@wordpress/i18n';
+import { mediaUpload } from '@wordpress/utils';
+import { Dashicon, Toolbar, Placeholder, FormFileUpload } from '@wordpress/components';
+
+/**
+ * Internal dependencies
+ */
+import MediaUploadButton from '../../media-upload-button';
+import InspectorControls from '../../inspector-controls';
+import RangeControl from '../../inspector-controls/range-control';
+import ToggleControl from '../../inspector-controls/toggle-control';
+import SelectControl from '../../inspector-controls/select-control';
+import BlockControls from '../../block-controls';
+import BlockAlignmentToolbar from '../../block-alignment-toolbar';
+import GalleryImage from './gallery-image';
+import BlockDescription from '../../block-description';
+
+const isGallery = true;
+const MAX_COLUMNS = 8;
+const linkOptions = [
+ { value: 'attachment', label: __( 'Attachment Page' ) },
+ { value: 'media', label: __( 'Media File' ) },
+ { value: 'none', label: __( 'None' ) },
+];
+
+export function defaultColumnsNumber( attributes ) {
+ return Math.min( 3, attributes.images.length );
+}
+
+class GalleryBlock extends Component {
+ constructor() {
+ super( ...arguments );
+
+ this.onSelectImage = this.onSelectImage.bind( this );
+ this.onSelectImages = this.onSelectImages.bind( this );
+ this.setLinkTo = this.setLinkTo.bind( this );
+ this.setColumnsNumber = this.setColumnsNumber.bind( this );
+ this.updateAlignment = this.updateAlignment.bind( this );
+ this.toggleImageCrop = this.toggleImageCrop.bind( this );
+ this.uploadFromFiles = this.uploadFromFiles.bind( this );
+ this.onRemoveImage = this.onRemoveImage.bind( this );
+
+ this.state = {
+ selectedImage: null,
+ };
+ }
+
+ onSelectImage( index ) {
+ return () => {
+ this.setState( ( state ) => ( {
+ selectedImage: index === state.selectedImage ? null : index,
+ } ) );
+ };
+ }
+
+ onRemoveImage( index ) {
+ return () => {
+ this.props.setAttributes( {
+ images: filter( this.props.attributes.images, ( img, i ) => index !== i ),
+ } );
+ };
+ }
+
+ onSelectImages( imgs ) {
+ this.props.setAttributes( { images: imgs } );
+ }
+
+ setLinkTo( value ) {
+ this.props.setAttributes( { linkTo: value } );
+ }
+
+ setColumnsNumber( value ) {
+ this.props.setAttributes( { columns: value } );
+ }
+
+ updateAlignment( nextAlign ) {
+ this.props.setAttributes( { align: nextAlign } );
+ }
+
+ toggleImageCrop() {
+ this.props.setAttributes( { imageCrop: ! this.props.attributes.imageCrop } );
+ }
+
+ uploadFromFiles( event ) {
+ mediaUpload( event.target.files, this.props.setAttributes, isGallery );
+ }
+
+ render() {
+ const { attributes, focus, className } = this.props;
+ const { images, columns = defaultColumnsNumber( attributes ), align, imageCrop, linkTo } = attributes;
+
+ const controls = (
+ focus && (
+
+
+ { !! images.length && (
+
+
+ img.id ) }
+ >
+
+
+
+
+ ) }
+
+ )
+ );
+
+ if ( images.length === 0 ) {
+ const uploadButtonProps = { isLarge: true };
+
+ return [
+ controls,
+
+
+ { __( 'Upload' ) }
+
+
+ { __( 'Insert from Media Library' ) }
+
+ ,
+ ];
+ }
+
+ return [
+ controls,
+ focus && images.length > 1 && (
+
+
+ { __( 'Image galleries are a great way to share groups of pictures on your site.' ) }
+
+ { __( 'Gallery Settings' ) }
+
+
+
+
+ ),
+
+ { images.map( ( img, index ) => (
+
+ ) ) }
+
,
+ ];
+ }
+}
+
+export default GalleryBlock;
diff --git a/blocks/library/gallery/editor.scss b/blocks/library/gallery/editor.scss
index 550a81048e536b..11e0077c8e19ed 100644
--- a/blocks/library/gallery/editor.scss
+++ b/blocks/library/gallery/editor.scss
@@ -6,3 +6,26 @@
.editor-visual-editor__block[data-type="core/gallery"] .editor-visual-editor__block-edit {
overflow: hidden;
}
+
+.blocks-gallery-image {
+ position: relative;
+
+ &.is-selected {
+ outline: 4px solid $blue-medium-500;
+ outline-offset: -4px;
+ }
+}
+
+.blocks-gallery-image__inline-menu {
+ position: absolute;
+ top: 8px;
+ right: 8px;
+ border: 1px solid $light-gray-500;
+ background-color: $white;
+ display: inline-flex;
+ z-index: z-index( '.blocks-gallery-image__inline-menu' );
+}
+
+.blocks-gallery-image__remove {
+ padding: 0;
+}
diff --git a/blocks/library/gallery/gallery-image.js b/blocks/library/gallery/gallery-image.js
index 7f113901f0807f..9369e622bd6c27 100644
--- a/blocks/library/gallery/gallery-image.js
+++ b/blocks/library/gallery/gallery-image.js
@@ -1,3 +1,13 @@
+/**
+ * External Depenedencies
+ */
+import classnames from 'classnames';
+
+/**
+ * WordPress Dependencies
+ */
+import { IconButton } from '@wordpress/components';
+import { __ } from '@wordpress/i18n';
export default function GalleryImage( props ) {
let href = null;
@@ -11,10 +21,26 @@ export default function GalleryImage( props ) {
}
const image = ;
+ const className = classnames( 'blocks-gallery-image', {
+ 'is-selected': props.isSelected,
+ } );
+ // Disable reason: Each block can be selected by clicking on it and we should keep the same saved markup
+ /* eslint-disable jsx-a11y/no-static-element-interactions, jsx-a11y/onclick-has-role, jsx-a11y/click-events-have-key-events */
return (
-