From 7f6ebd76ca96fa7bed8d0be75cf52139e6eee224 Mon Sep 17 00:00:00 2001 From: Chris Swithinbank Date: Wed, 20 May 2026 18:12:11 +0200 Subject: [PATCH 1/8] fix: support small viewport in virtualized media grid Calculation would result in a column count of 0 which would error. This is now clamped to a minimum of 1. --- .../src/components/MediaLibrary/MediaLibraryCard.js | 4 +--- .../src/components/MediaLibrary/MediaLibraryCardGrid.js | 4 ++-- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/packages/decap-cms-core/src/components/MediaLibrary/MediaLibraryCard.js b/packages/decap-cms-core/src/components/MediaLibrary/MediaLibraryCard.js index fa954f3bb792..af279a19f040 100644 --- a/packages/decap-cms-core/src/components/MediaLibrary/MediaLibraryCard.js +++ b/packages/decap-cms-core/src/components/MediaLibrary/MediaLibraryCard.js @@ -70,7 +70,6 @@ class MediaLibraryCard extends React.Component { text, onClick, draftText, - width, height, margin, isPrivate, @@ -83,7 +82,7 @@ class MediaLibraryCard extends React.Component { Date: Wed, 20 May 2026 18:14:14 +0200 Subject: [PATCH 2/8] fix: rework layout of media modal header to be responsive --- .../MediaLibrary/MediaLibraryButtons.js | 2 -- .../MediaLibrary/MediaLibraryHeader.js | 11 ++++++---- .../MediaLibrary/MediaLibrarySearch.js | 4 +++- .../MediaLibrary/MediaLibraryTop.js | 20 ++++++++++++++++++- 4 files changed, 29 insertions(+), 8 deletions(-) diff --git a/packages/decap-cms-core/src/components/MediaLibrary/MediaLibraryButtons.js b/packages/decap-cms-core/src/components/MediaLibrary/MediaLibraryButtons.js index fa3eb19b057e..a5c7fa9c136c 100644 --- a/packages/decap-cms-core/src/components/MediaLibrary/MediaLibraryButtons.js +++ b/packages/decap-cms-core/src/components/MediaLibrary/MediaLibraryButtons.js @@ -12,8 +12,6 @@ const styles = { ${buttons.button}; ${buttons.default}; display: inline-block; - margin-left: 15px; - margin-right: 2px; &[disabled] { ${buttons.disabled}; diff --git a/packages/decap-cms-core/src/components/MediaLibrary/MediaLibraryHeader.js b/packages/decap-cms-core/src/components/MediaLibrary/MediaLibraryHeader.js index 54c44cf54d1f..45f85ac6a593 100644 --- a/packages/decap-cms-core/src/components/MediaLibrary/MediaLibraryHeader.js +++ b/packages/decap-cms-core/src/components/MediaLibrary/MediaLibraryHeader.js @@ -21,10 +21,13 @@ const CloseButton = styled.button` `; const LibraryTitle = styled.h1` - line-height: 36px; - font-size: 22px; - text-align: left; - margin-bottom: 25px; + font-size: 18px; + line-height: 28px; + @media (min-width: 800px) { + font-size: 22px; + line-height: 36px; + } + margin-bottom: 0; color: ${props => props.isPrivate && colors.textFieldBorder}; `; diff --git a/packages/decap-cms-core/src/components/MediaLibrary/MediaLibrarySearch.js b/packages/decap-cms-core/src/components/MediaLibrary/MediaLibrarySearch.js index 54629f373387..ec49a871a2c0 100644 --- a/packages/decap-cms-core/src/components/MediaLibrary/MediaLibrarySearch.js +++ b/packages/decap-cms-core/src/components/MediaLibrary/MediaLibrarySearch.js @@ -8,7 +8,8 @@ const SearchContainer = styled.div` display: flex; align-items: center; position: relative; - width: 400px; + flex: 1 1 150px; + max-width: 460px; `; const SearchInput = styled.input` @@ -33,6 +34,7 @@ const SearchIcon = styled(Icon)` left: 6px; z-index: ${zIndex.zIndex2}; transform: translate(0, -50%); + pointer-events: none; `; function MediaLibrarySearch({ value, onChange, onKeyDown, placeholder, disabled }) { diff --git a/packages/decap-cms-core/src/components/MediaLibrary/MediaLibraryTop.js b/packages/decap-cms-core/src/components/MediaLibrary/MediaLibraryTop.js index 524b779a4008..88491dbee906 100644 --- a/packages/decap-cms-core/src/components/MediaLibrary/MediaLibraryTop.js +++ b/packages/decap-cms-core/src/components/MediaLibrary/MediaLibraryTop.js @@ -1,5 +1,6 @@ import React from 'react'; import PropTypes from 'prop-types'; +import { css } from '@emotion/react'; import styled from '@emotion/styled'; import MediaLibrarySearch from './MediaLibrarySearch'; @@ -12,19 +13,36 @@ import { InsertButton, } from './MediaLibraryButtons'; +const styles = { + flexSpacing: css` + gap: 8px; + @media (min-width: 500px) { + gap: 15px; + } + `, +}; + const LibraryTop = styled.div` position: relative; display: flex; flex-direction: column; + ${styles.flexSpacing}; `; const RowContainer = styled.div` display: flex; + align-items: center; justify-content: space-between; + flex-wrap: wrap; + ${styles.flexSpacing}; `; const ButtonsContainer = styled.div` - flex-shrink: 0; + display: flex; + align-items: center; + justify-content: stretch; + flex-wrap: wrap; + ${styles.flexSpacing}; `; function MediaLibraryTop({ From afe4cd2d17856fe67852017eb4c289501ddc6c3d Mon Sep 17 00:00:00 2001 From: Chris Swithinbank Date: Wed, 20 May 2026 18:15:13 +0200 Subject: [PATCH 3/8] feat: use icons for copy & download buttons in small screens --- .../MediaLibrary/MediaLibraryButtons.js | 44 ++++++++++++++++--- .../src/Icon/images/_index.js | 4 ++ .../src/Icon/images/copy.svg | 1 + .../src/Icon/images/download.svg | 1 + 4 files changed, 45 insertions(+), 5 deletions(-) create mode 100644 packages/decap-cms-ui-default/src/Icon/images/copy.svg create mode 100644 packages/decap-cms-ui-default/src/Icon/images/download.svg diff --git a/packages/decap-cms-core/src/components/MediaLibrary/MediaLibraryButtons.js b/packages/decap-cms-core/src/components/MediaLibrary/MediaLibraryButtons.js index a5c7fa9c136c..9baacd7dbc10 100644 --- a/packages/decap-cms-core/src/components/MediaLibrary/MediaLibraryButtons.js +++ b/packages/decap-cms-core/src/components/MediaLibrary/MediaLibraryButtons.js @@ -3,7 +3,7 @@ import PropTypes from 'prop-types'; import { css } from '@emotion/react'; import styled from '@emotion/styled'; import { isAbsolutePath } from 'decap-cms-lib-util'; -import { buttons, shadows, zIndex } from 'decap-cms-ui-default'; +import { buttons, Icon, shadows, zIndex } from 'decap-cms-ui-default'; import { FileUploadButton } from '../UI'; @@ -11,13 +11,25 @@ const styles = { button: css` ${buttons.button}; ${buttons.default}; - display: inline-block; + display: inline-flex; + align-items: center; &[disabled] { ${buttons.disabled}; cursor: default; } `, + // Hides an element without removing it from the accessibility tree. + visuallyHidden: css` + position: absolute; + width: 1px; + height: 1px; + padding: 0; + margin: -1px; + overflow: hidden; + clip: rect(0, 0, 0, 0); + white-space: nowrap; + `, }; export const UploadButton = styled(FileUploadButton)` @@ -66,7 +78,27 @@ const ActionButton = styled.button` `} `; -export const DownloadButton = ActionButton; +function ResponsiveActionButton({ children, icon, ...props }) { + return ( + + + ); +} + +export function DownloadButton({ children, ...props }) { + return ( + + {children} + + ); +} export class CopyToClipBoardButton extends React.Component { mounted = false; @@ -113,13 +145,15 @@ export class CopyToClipBoardButton extends React.Component { return t('mediaLibrary.mediaLibraryCard.copyPath'); }; + getIcon = () => (this.state.copied ? 'check' : 'copy'); + render() { const { disabled } = this.props; return ( - + {this.getTitle()} - + ); } } diff --git a/packages/decap-cms-ui-default/src/Icon/images/_index.js b/packages/decap-cms-ui-default/src/Icon/images/_index.js index 80408dfeeb02..6407adca15b9 100644 --- a/packages/decap-cms-ui-default/src/Icon/images/_index.js +++ b/packages/decap-cms-ui-default/src/Icon/images/_index.js @@ -11,6 +11,8 @@ import iconCircle from './circle.svg'; import iconClose from './close.svg'; import iconCode from './code.svg'; import iconCodeBlock from './code-block.svg'; +import iconCopy from './copy.svg'; +import iconDownload from './download.svg'; import iconDragHandle from './drag-handle.svg'; import iconEye from './eye.svg'; import iconFolder from './folder.svg'; @@ -63,6 +65,8 @@ const images = { close: iconClose, code: iconCode, 'code-block': iconCodeBlock, + copy: iconCopy, + download: iconDownload, 'drag-handle': iconDragHandle, eye: iconEye, folder: iconFolder, diff --git a/packages/decap-cms-ui-default/src/Icon/images/copy.svg b/packages/decap-cms-ui-default/src/Icon/images/copy.svg new file mode 100644 index 000000000000..9080ae6e2611 --- /dev/null +++ b/packages/decap-cms-ui-default/src/Icon/images/copy.svg @@ -0,0 +1 @@ + diff --git a/packages/decap-cms-ui-default/src/Icon/images/download.svg b/packages/decap-cms-ui-default/src/Icon/images/download.svg new file mode 100644 index 000000000000..bbf8d94ba1bb --- /dev/null +++ b/packages/decap-cms-ui-default/src/Icon/images/download.svg @@ -0,0 +1 @@ + From 43eb94753008e13ee262357183290567c063e06c Mon Sep 17 00:00:00 2001 From: Chris Swithinbank Date: Wed, 20 May 2026 18:15:57 +0200 Subject: [PATCH 4/8] fix: let modal layout adapt to varying header heights --- .../src/components/MediaLibrary/MediaLibraryModal.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/decap-cms-core/src/components/MediaLibrary/MediaLibraryModal.js b/packages/decap-cms-core/src/components/MediaLibrary/MediaLibraryModal.js index ba89b25517c0..7cb6e2d8c2ae 100644 --- a/packages/decap-cms-core/src/components/MediaLibrary/MediaLibraryModal.js +++ b/packages/decap-cms-core/src/components/MediaLibrary/MediaLibraryModal.js @@ -27,8 +27,9 @@ const cardOutsideWidth = `300px`; const StyledModal = styled(Modal)` display: grid; - grid-template-rows: 120px auto; width: calc(${cardOutsideWidth} + 20px); + grid-template-rows: auto 1fr; + gap: 20px; background-color: ${props => props.isPrivate && colors.grayDark}; @media (min-width: 800px) { From dbed762493114d7150bbd2e6452e6408c69df469 Mon Sep 17 00:00:00 2001 From: Chris Swithinbank Date: Wed, 20 May 2026 18:18:02 +0200 Subject: [PATCH 5/8] fix: make minimum width of modal responsive --- .../src/components/MediaLibrary/MediaLibraryModal.js | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/packages/decap-cms-core/src/components/MediaLibrary/MediaLibraryModal.js b/packages/decap-cms-core/src/components/MediaLibrary/MediaLibraryModal.js index 7cb6e2d8c2ae..213f14ea4b9b 100644 --- a/packages/decap-cms-core/src/components/MediaLibrary/MediaLibraryModal.js +++ b/packages/decap-cms-core/src/components/MediaLibrary/MediaLibraryModal.js @@ -25,17 +25,16 @@ const cardMargin = `10px`; */ const cardOutsideWidth = `300px`; +/** Calculated to be as wide as possible without cutting off the 40px-wide close button. */ +const minimumCardWidth = `calc(100% - 40px)`; + const StyledModal = styled(Modal)` display: grid; - width: calc(${cardOutsideWidth} + 20px); + width: min(${minimumCardWidth}, calc(${cardOutsideWidth} * 2 + 20px)); grid-template-rows: auto 1fr; gap: 20px; background-color: ${props => props.isPrivate && colors.grayDark}; - @media (min-width: 800px) { - width: calc(${cardOutsideWidth} * 2 + 20px); - } - @media (min-width: 1120px) { width: calc(${cardOutsideWidth} * 3 + 20px); } From 5a88052adaf36bae206a464b7a5d4dc424002160 Mon Sep 17 00:00:00 2001 From: Chris Swithinbank Date: Wed, 20 May 2026 18:35:35 +0200 Subject: [PATCH 6/8] feat: use full viewport width for modal on small screens --- .../MediaLibrary/MediaLibraryHeader.js | 41 ++++++++++++------- .../MediaLibrary/MediaLibraryModal.js | 6 ++- .../decap-cms-core/src/components/UI/Modal.js | 13 ++++-- 3 files changed, 41 insertions(+), 19 deletions(-) diff --git a/packages/decap-cms-core/src/components/MediaLibrary/MediaLibraryHeader.js b/packages/decap-cms-core/src/components/MediaLibrary/MediaLibraryHeader.js index 45f85ac6a593..76edc5710ea7 100644 --- a/packages/decap-cms-core/src/components/MediaLibrary/MediaLibraryHeader.js +++ b/packages/decap-cms-core/src/components/MediaLibrary/MediaLibraryHeader.js @@ -3,27 +3,38 @@ import PropTypes from 'prop-types'; import styled from '@emotion/styled'; import { Icon, shadows, colors, buttons } from 'decap-cms-ui-default'; +const HeaderContainer = styled.div` + @media (max-width: 499px) { + width: 100%; + display: flex; + justify-content: space-between; + flex-direction: row-reverse; + } +`; + const CloseButton = styled.button` ${buttons.button}; - ${shadows.dropMiddle}; - position: absolute; - margin-right: -40px; - left: -40px; - top: -40px; - width: 40px; - height: 40px; - border-radius: 50%; background-color: white; - padding: 0; - display: flex; - justify-content: center; - align-items: center; + @media (min-width: 500px) { + ${shadows.dropMiddle}; + position: absolute; + margin-right: -40px; + left: -40px; + top: -40px; + width: 40px; + height: 40px; + border-radius: 50%; + padding: 0; + display: flex; + justify-content: center; + align-items: center; + } `; const LibraryTitle = styled.h1` font-size: 18px; line-height: 28px; - @media (min-width: 800px) { + @media (min-width: 500px) { font-size: 22px; line-height: 36px; } @@ -33,12 +44,12 @@ const LibraryTitle = styled.h1` function MediaLibraryHeader({ onClose, title, isPrivate, t }) { return ( -
+ {title} -
+ ); } diff --git a/packages/decap-cms-core/src/components/MediaLibrary/MediaLibraryModal.js b/packages/decap-cms-core/src/components/MediaLibrary/MediaLibraryModal.js index 213f14ea4b9b..6648d5b5abdf 100644 --- a/packages/decap-cms-core/src/components/MediaLibrary/MediaLibraryModal.js +++ b/packages/decap-cms-core/src/components/MediaLibrary/MediaLibraryModal.js @@ -30,11 +30,15 @@ const minimumCardWidth = `calc(100% - 40px)`; const StyledModal = styled(Modal)` display: grid; - width: min(${minimumCardWidth}, calc(${cardOutsideWidth} * 2 + 20px)); + width: 100%; grid-template-rows: auto 1fr; gap: 20px; background-color: ${props => props.isPrivate && colors.grayDark}; + @media (min-width: 500px) { + width: min(${minimumCardWidth}, calc(${cardOutsideWidth} * 2 + 20px)); + } + @media (min-width: 1120px) { width: calc(${cardOutsideWidth} * 3 + 20px); } diff --git a/packages/decap-cms-core/src/components/UI/Modal.js b/packages/decap-cms-core/src/components/UI/Modal.js index 0388e21755ed..cc1ff0d1d596 100644 --- a/packages/decap-cms-core/src/components/UI/Modal.js +++ b/packages/decap-cms-core/src/components/UI/Modal.js @@ -20,8 +20,12 @@ const styleStrings = { modalBody: ` ${shadows.dropDeep}; background-color: #fff; - border-radius: ${lengths.borderRadius}; - height: 80%; + border-radius: ${lengths.borderRadius} ${lengths.borderRadius} 0 0; + height: 90%; + @media (min-width: 500px) { + border-radius: ${lengths.borderRadius}; + height: 80%; + } text-align: center; max-width: 2200px; padding: 20px; @@ -39,7 +43,10 @@ const styleStrings = { bottom: 0; display: flex; justify-content: center; - align-items: center; + align-items: flex-end; + @media (min-width: 500px) { + align-items: center; + } opacity: 0; background-color: rgba(0, 0, 0, 0); transition: background-color ${transitions.main}, opacity ${transitions.main}; From 1abbe0ac5319f9b1cadca8dcfb8bb7b8953e1138 Mon Sep 17 00:00:00 2001 From: Chris Swithinbank Date: Wed, 20 May 2026 18:37:25 +0200 Subject: [PATCH 7/8] fix: revert accidental style deletion --- .../src/components/MediaLibrary/MediaLibraryHeader.js | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/decap-cms-core/src/components/MediaLibrary/MediaLibraryHeader.js b/packages/decap-cms-core/src/components/MediaLibrary/MediaLibraryHeader.js index 76edc5710ea7..9239fc0464e3 100644 --- a/packages/decap-cms-core/src/components/MediaLibrary/MediaLibraryHeader.js +++ b/packages/decap-cms-core/src/components/MediaLibrary/MediaLibraryHeader.js @@ -34,6 +34,7 @@ const CloseButton = styled.button` const LibraryTitle = styled.h1` font-size: 18px; line-height: 28px; + text-align: left; @media (min-width: 500px) { font-size: 22px; line-height: 36px; From 30e27efca62c3175fa05e15e9c7141665e8cf9cc Mon Sep 17 00:00:00 2001 From: Chris Swithinbank Date: Wed, 20 May 2026 19:06:53 +0200 Subject: [PATCH 8/8] test: fix test snapshot for card grid changes --- .../__snapshots__/MediaLibraryCard.spec.js.snap | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/decap-cms-core/src/components/MediaLibrary/__tests__/__snapshots__/MediaLibraryCard.spec.js.snap b/packages/decap-cms-core/src/components/MediaLibrary/__tests__/__snapshots__/MediaLibraryCard.spec.js.snap index 24bbf107412d..4cf26b9d09ce 100644 --- a/packages/decap-cms-core/src/components/MediaLibrary/__tests__/__snapshots__/MediaLibraryCard.spec.js.snap +++ b/packages/decap-cms-core/src/components/MediaLibrary/__tests__/__snapshots__/MediaLibraryCard.spec.js.snap @@ -3,7 +3,7 @@ exports[`MediaLibraryCard should match snapshot for draft image 1`] = ` .emotion-0 { - width: 100px; + width: 100%; height: 240px; margin: 10px; border: solid 2px #dfdfe3; @@ -70,7 +70,7 @@ exports[`MediaLibraryCard should match snapshot for draft image 1`] = ` class="emotion-0 emotion-1" height="240px" tabindex="-1" - width="100px" + width="100%" >
.emotion-0 { - width: 100px; + width: 100%; height: 240px; margin: 10px; border: solid 2px #dfdfe3; @@ -158,7 +158,7 @@ exports[`MediaLibraryCard should match snapshot for non draft image 1`] = ` class="emotion-0 emotion-1" height="240px" tabindex="-1" - width="100px" + width="100%" >
.emotion-0 { - width: 100px; + width: 100%; height: 240px; margin: 10px; border: solid 2px #dfdfe3; @@ -242,7 +242,7 @@ exports[`MediaLibraryCard should match snapshot for non viewable image 1`] = ` class="emotion-0 emotion-1" height="240px" tabindex="-1" - width="100px" + width="100%" >