diff --git a/src/admin/components/elements/UploadCard/index.scss b/src/admin/components/elements/ThumbnailCard/index.scss similarity index 54% rename from src/admin/components/elements/UploadCard/index.scss rename to src/admin/components/elements/ThumbnailCard/index.scss index ede6ac8727..d3cba266bb 100644 --- a/src/admin/components/elements/UploadCard/index.scss +++ b/src/admin/components/elements/ThumbnailCard/index.scss @@ -1,19 +1,28 @@ @import '../../../scss/styles.scss'; -.upload-card { +.thumbnail-card { @include shadow; background: var(--theme-input-bg); - max-width: base(9); - margin-bottom: base(.5); - &__filename { + &__label { padding: base(.5); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; + font-weight: 600; } &--has-on-click { cursor: pointer; } + + &--align-label-center { + text-align: center; + } + + &__thumbnail { + display: flex; + align-items: center; + justify-content: center; + } } diff --git a/src/admin/components/elements/ThumbnailCard/index.tsx b/src/admin/components/elements/ThumbnailCard/index.tsx new file mode 100644 index 0000000000..92e28769a9 --- /dev/null +++ b/src/admin/components/elements/ThumbnailCard/index.tsx @@ -0,0 +1,57 @@ +import React, { Fragment } from 'react'; +import { useTranslation } from 'react-i18next'; +import { Props } from './types'; +import Thumbnail from '../Thumbnail'; + +import './index.scss'; + +const baseClass = 'thumbnail-card'; + +export const ThumbnailCard: React.FC = (props) => { + const { + className, + onClick, + doc, + collection, + thumbnail, + label, + alignLabel, + onKeyDown, + } = props; + + const { t } = useTranslation('general'); + + const classes = [ + baseClass, + className, + typeof onClick === 'function' && `${baseClass}--has-on-click`, + alignLabel && `${baseClass}--align-label-${alignLabel}`, + ].filter(Boolean).join(' '); + + return ( +
+
+ {thumbnail && thumbnail} + {!thumbnail && (collection && doc) && ( + + )} +
+
+ {label && label} + {!label && doc && ( + + {typeof doc?.filename === 'string' ? doc?.filename : `[${t('untitled')}]`} + + )} +
+
+ ); +}; diff --git a/src/admin/components/elements/ThumbnailCard/types.ts b/src/admin/components/elements/ThumbnailCard/types.ts new file mode 100644 index 0000000000..29ca65b5fb --- /dev/null +++ b/src/admin/components/elements/ThumbnailCard/types.ts @@ -0,0 +1,12 @@ +import { SanitizedCollectionConfig } from '../../../../collections/config/types'; + +export type Props = { + className?: string + collection?: SanitizedCollectionConfig + doc?: Record + onClick?: () => void + onKeyDown?: () => void + thumbnail?: React.ReactNode + label?: string + alignLabel?: 'left' | 'center' +} diff --git a/src/admin/components/elements/UploadCard/index.tsx b/src/admin/components/elements/UploadCard/index.tsx deleted file mode 100644 index 3fe17f3626..0000000000 --- a/src/admin/components/elements/UploadCard/index.tsx +++ /dev/null @@ -1,44 +0,0 @@ -/* eslint-disable jsx-a11y/click-events-have-key-events */ -import React from 'react'; -import { useTranslation } from 'react-i18next'; -import { Props } from './types'; -import Thumbnail from '../Thumbnail'; - -import './index.scss'; - -const baseClass = 'upload-card'; - -const UploadCard: React.FC = (props) => { - const { - className, - onClick, - doc, - collection, - } = props; - - const { t } = useTranslation('general'); - - const classes = [ - baseClass, - className, - typeof onClick === 'function' && `${baseClass}--has-on-click`, - ].filter(Boolean).join(' '); - - return ( -
- -
- {typeof doc?.filename === 'string' ? doc?.filename : `[${t('untitled')}]`} -
-
- ); -}; - -export default UploadCard; diff --git a/src/admin/components/elements/UploadCard/types.ts b/src/admin/components/elements/UploadCard/types.ts deleted file mode 100644 index 7ebfd460b3..0000000000 --- a/src/admin/components/elements/UploadCard/types.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { SanitizedCollectionConfig } from '../../../../collections/config/types'; - -export type Props = { - className?: string - collection: SanitizedCollectionConfig, - doc: Record - onClick?: () => void, -} diff --git a/src/admin/components/elements/UploadGallery/index.scss b/src/admin/components/elements/UploadGallery/index.scss index b7b722f72a..f63956edab 100644 --- a/src/admin/components/elements/UploadGallery/index.scss +++ b/src/admin/components/elements/UploadGallery/index.scss @@ -13,7 +13,7 @@ width: 16.66%; } - .upload-card { + .thumbnail-card { margin: base(.5); max-width: initial; } diff --git a/src/admin/components/elements/UploadGallery/index.tsx b/src/admin/components/elements/UploadGallery/index.tsx index 98730cf847..fdccc6c040 100644 --- a/src/admin/components/elements/UploadGallery/index.tsx +++ b/src/admin/components/elements/UploadGallery/index.tsx @@ -1,6 +1,6 @@ import React from 'react'; import { Props } from './types'; -import UploadCard from '../UploadCard'; +import { ThumbnailCard } from '../ThumbnailCard'; import './index.scss'; @@ -14,9 +14,9 @@ const UploadGallery: React.FC = (props) => {
    {docs.map((doc) => (
  • - onCardClick(doc)} />
  • diff --git a/src/admin/components/forms/field-types/Blocks/BlocksDrawer/BlockSearch/index.scss b/src/admin/components/forms/field-types/Blocks/BlocksDrawer/BlockSearch/index.scss index e6b8991891..e6afb0827b 100644 --- a/src/admin/components/forms/field-types/Blocks/BlocksDrawer/BlockSearch/index.scss +++ b/src/admin/components/forms/field-types/Blocks/BlocksDrawer/BlockSearch/index.scss @@ -7,6 +7,7 @@ $icon-margin: base(.25); position: sticky; top: 0; display: flex; + width: 100%; align-items: center; z-index: 1; @@ -17,9 +18,15 @@ $icon-margin: base(.25); .search { position: absolute; + top: 50%; + transform: translate3d(0, -50%, 0); right: base(.25); width: $icon-width; margin: 0 $icon-margin; + + .stroke { + stroke: var(--theme-elevation-300); + } } @include mid-break { diff --git a/src/admin/components/forms/field-types/Blocks/BlocksDrawer/BlockSelection/index.scss b/src/admin/components/forms/field-types/Blocks/BlocksDrawer/BlockSelection/index.scss deleted file mode 100644 index 37334a78a0..0000000000 --- a/src/admin/components/forms/field-types/Blocks/BlocksDrawer/BlockSelection/index.scss +++ /dev/null @@ -1,39 +0,0 @@ -@import '../../../../../../scss/styles'; - -.block-selection { - display: inline-flex; - flex-direction: column; - flex-wrap: wrap; - width: 33%; - padding: base(.75) base(.5); - cursor: pointer; - align-items: center; - background: none; - border-radius: 0; - box-shadow: 0; - border: 0; - color: currentColor; - - &:hover { - background-color: var(--theme-elevation-100); - } - - &__image { - - svg, - img { - max-width: 100%; - } - } - - &__label { - margin-top: base(.25); - font-weight: 600; - text-align: center; - white-space: initial; - } - - @include mid-break { - width: unset; - } -} diff --git a/src/admin/components/forms/field-types/Blocks/BlocksDrawer/BlockSelection/index.tsx b/src/admin/components/forms/field-types/Blocks/BlocksDrawer/BlockSelection/index.tsx deleted file mode 100644 index 03f5625115..0000000000 --- a/src/admin/components/forms/field-types/Blocks/BlocksDrawer/BlockSelection/index.tsx +++ /dev/null @@ -1,52 +0,0 @@ -import React, { useCallback } from 'react'; -import { useTranslation } from 'react-i18next'; -import { getTranslation } from '../../../../../../../utilities/getTranslation'; -import DefaultBlockImage from '../../../../../graphics/DefaultBlockImage'; -import { Props } from './types'; - -import './index.scss'; - -const baseClass = 'block-selection'; - -const BlockSelection: React.FC = (props) => { - const { - onClick, - addRow, - addRowIndex, - block, - } = props; - - const { i18n } = useTranslation(); - - const { - labels, slug, imageURL, imageAltText, - } = block; - - const handleBlockSelection = useCallback(() => { - addRow(addRowIndex, slug); - if (typeof onClick === 'function') { - onClick(); - } - }, [onClick, addRow, addRowIndex, slug]); - - return ( - - ); -}; - -export default BlockSelection; diff --git a/src/admin/components/forms/field-types/Blocks/BlocksDrawer/BlockSelection/types.ts b/src/admin/components/forms/field-types/Blocks/BlocksDrawer/BlockSelection/types.ts deleted file mode 100644 index 2a35a01d34..0000000000 --- a/src/admin/components/forms/field-types/Blocks/BlocksDrawer/BlockSelection/types.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { Block } from '../../../../../../../fields/config/types'; - -export type Props = { - addRow: (i: number, block: string) => void - addRowIndex: number - block: Block - onClick?: () => void -} diff --git a/src/admin/components/forms/field-types/Blocks/BlocksDrawer/index.scss b/src/admin/components/forms/field-types/Blocks/BlocksDrawer/index.scss index c039c09a11..7ab15fe273 100644 --- a/src/admin/components/forms/field-types/Blocks/BlocksDrawer/index.scss +++ b/src/admin/components/forms/field-types/Blocks/BlocksDrawer/index.scss @@ -1,10 +1,62 @@ @import '../../../../../scss/styles.scss'; .blocks-drawer { - margin-top: base(2.5); - width: 100%; + &__wrapper { + margin-top: base(2.5); + width: 100%; + } - @include mid-break { + &__blocks-wrapper { + padding: base(0.5); margin-top: base(1.5); } + + &__blocks { + position: relative; + margin: -#{base(1)}; + display: flex; + flex-wrap: wrap; + padding: 0; + list-style: none; + } + + &__block { + margin: base(0.5); + width: calc(25% - #{base(1)}); + } + + &__default-image { + width: 100%; + overflow: hidden; + padding-top: base(0.75); + } + + @include mid-break { + &__wrapper { + margin-top: base(1.5); + } + + &__blocks-wrapper { + padding: base(0.25); + } + + &__blocks { + margin: -#{base(0.5)}; + } + + &__block { + margin: base(0.25); + width: calc(33.33% - #{base(0.5)}); + } + } + + @include small-break { + &__blocks-wrapper { + margin-top: base(0.75); + } + + &__block { + width: calc(50% - #{base(0.5)}); + } + } } diff --git a/src/admin/components/forms/field-types/Blocks/BlocksDrawer/index.tsx b/src/admin/components/forms/field-types/Blocks/BlocksDrawer/index.tsx index ec17dcf52f..a80698f2e5 100644 --- a/src/admin/components/forms/field-types/Blocks/BlocksDrawer/index.tsx +++ b/src/admin/components/forms/field-types/Blocks/BlocksDrawer/index.tsx @@ -3,10 +3,11 @@ import { useModal } from '@faceless-ui/modal'; import { useTranslation } from 'react-i18next'; import BlockSearch from './BlockSearch'; import { Props } from './types'; -import BlockSelection from './BlockSelection'; import { Drawer } from '../../../../elements/Drawer'; import { Gutter } from '../../../../elements/Gutter'; import { getTranslation } from '../../../../../../utilities/getTranslation'; +import { ThumbnailCard } from '../../../../elements/ThumbnailCard'; +import DefaultBlockImage from '../../../../graphics/DefaultBlockImage'; import './index.scss'; @@ -37,23 +38,48 @@ export const BlocksDrawer: React.FC = (props) => { return ( - +

    {t('addLabel', { label: getTranslation(labels.singular, i18n) })}

    -
    - {filteredBlocks?.map((block, index) => ( - { - closeModal(drawerSlug); - }} - addRow={addRow} - addRowIndex={addRowIndex} - /> - ))} +
    +
      + {filteredBlocks?.map((block, index) => { + const { + labels: blockLabels, + slug, + imageURL, + imageAltText, + } = block; + + return ( +
    • + { + addRow(addRowIndex, slug); + closeModal(drawerSlug); + }} + thumbnail={imageURL ? ( + {imageAltText} + ) : ( +
      + +
      + )} + label={getTranslation(blockLabels.singular, i18n)} + alignLabel="center" + /> +
    • + ); + })} +
    diff --git a/src/admin/scss/vars.scss b/src/admin/scss/vars.scss index 3fcfe88cd3..745747d5ee 100644 --- a/src/admin/scss/vars.scss +++ b/src/admin/scss/vars.scss @@ -81,10 +81,10 @@ $focus-box-shadow: 0 0 0 $style-stroke-width-m var(--theme-success-500); } @mixin shadow { - box-shadow: 0 12px 45px rgba(0, 0, 0, .03); + box-shadow: 0 4px 12px rgba(0, 0, 0, .07); &:hover { - box-shadow: 0 12px 45px rgba(0, 0, 0, .07); + box-shadow: 0 4px 12px rgba(0, 0, 0, .1); } } diff --git a/test/fields/e2e.spec.ts b/test/fields/e2e.spec.ts index 1656680b14..2c7c3334dc 100644 --- a/test/fields/e2e.spec.ts +++ b/test/fields/e2e.spec.ts @@ -207,7 +207,7 @@ describe('fields', () => { await expect(blocksDrawer).toBeVisible(); // select the first block in the drawer - const firstBlockSelector = await blocksDrawer.locator('.blocks-drawer .block-selection').first(); + const firstBlockSelector = await blocksDrawer.locator('.blocks-drawer__blocks .blocks-drawer__block').first(); await expect(firstBlockSelector).toContainText('Text'); await firstBlockSelector.click(); @@ -231,7 +231,7 @@ describe('fields', () => { await expect(blocksDrawer).toBeVisible(); // select the first block in the drawer - const firstBlockSelector = blocksDrawer.locator('.blocks-drawer .block-selection').first(); + const firstBlockSelector = blocksDrawer.locator('.blocks-drawer__blocks .blocks-drawer__block').first(); await expect(firstBlockSelector).toContainText('Text'); await firstBlockSelector.click(); @@ -253,7 +253,7 @@ describe('fields', () => { await expect(blocksDrawer).toBeVisible(); // select the first block in the drawer - const firstBlockSelector = blocksDrawer.locator('.blocks-drawer .block-selection').first(); + const firstBlockSelector = blocksDrawer.locator('.blocks-drawer__blocks .blocks-drawer__block').first(); await expect(firstBlockSelector).toContainText('Text en'); await firstBlockSelector.click(); @@ -426,7 +426,7 @@ describe('fields', () => { // Close the drawer await editLinkModal.locator('button[type="submit"]').click(); - await expect(editLinkModal).not.toBeVisible(); + await expect(editLinkModal).toBeHidden(); }); test('should populate relationship link', async () => { @@ -449,7 +449,7 @@ describe('fields', () => { // Close the drawer await editLinkModal.locator('button[type="submit"]').click(); - await expect(editLinkModal).not.toBeVisible(); + await expect(editLinkModal).toBeHidden(); }); test('should populate new links', async () => { diff --git a/test/uploads/e2e.spec.ts b/test/uploads/e2e.spec.ts index 71da43dab8..523046da59 100644 --- a/test/uploads/e2e.spec.ts +++ b/test/uploads/e2e.spec.ts @@ -49,7 +49,7 @@ describe('uploads', () => { test('should show upload filename in upload collection list', async () => { await page.goto(mediaURL.list); - const media = page.locator('.upload-card__filename'); + const media = page.locator('.thumbnail-card__label'); await wait(110); await expect(media).toHaveText('image.png');