chore: refines blocks drawer content
This commit is contained in:
@@ -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;
|
||||
}
|
||||
}
|
||||
57
src/admin/components/elements/ThumbnailCard/index.tsx
Normal file
57
src/admin/components/elements/ThumbnailCard/index.tsx
Normal file
@@ -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> = (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 (
|
||||
<div
|
||||
className={classes}
|
||||
onClick={typeof onClick === 'function' ? onClick : undefined}
|
||||
onKeyDown={typeof onKeyDown === 'function' ? onKeyDown : undefined}
|
||||
>
|
||||
<div className={`${baseClass}__thumbnail`}>
|
||||
{thumbnail && thumbnail}
|
||||
{!thumbnail && (collection && doc) && (
|
||||
<Thumbnail
|
||||
size="expand"
|
||||
doc={doc}
|
||||
collection={collection}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<div className={`${baseClass}__label`}>
|
||||
{label && label}
|
||||
{!label && doc && (
|
||||
<Fragment>
|
||||
{typeof doc?.filename === 'string' ? doc?.filename : `[${t('untitled')}]`}
|
||||
</Fragment>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
12
src/admin/components/elements/ThumbnailCard/types.ts
Normal file
12
src/admin/components/elements/ThumbnailCard/types.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import { SanitizedCollectionConfig } from '../../../../collections/config/types';
|
||||
|
||||
export type Props = {
|
||||
className?: string
|
||||
collection?: SanitizedCollectionConfig
|
||||
doc?: Record<string, unknown>
|
||||
onClick?: () => void
|
||||
onKeyDown?: () => void
|
||||
thumbnail?: React.ReactNode
|
||||
label?: string
|
||||
alignLabel?: 'left' | 'center'
|
||||
}
|
||||
@@ -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> = (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 (
|
||||
<div
|
||||
className={classes}
|
||||
onClick={typeof onClick === 'function' ? onClick : undefined}
|
||||
>
|
||||
<Thumbnail
|
||||
size="expand"
|
||||
doc={doc}
|
||||
collection={collection}
|
||||
/>
|
||||
<div className={`${baseClass}__filename`}>
|
||||
{typeof doc?.filename === 'string' ? doc?.filename : `[${t('untitled')}]`}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default UploadCard;
|
||||
@@ -1,8 +0,0 @@
|
||||
import { SanitizedCollectionConfig } from '../../../../collections/config/types';
|
||||
|
||||
export type Props = {
|
||||
className?: string
|
||||
collection: SanitizedCollectionConfig,
|
||||
doc: Record<string, unknown>
|
||||
onClick?: () => void,
|
||||
}
|
||||
@@ -13,7 +13,7 @@
|
||||
width: 16.66%;
|
||||
}
|
||||
|
||||
.upload-card {
|
||||
.thumbnail-card {
|
||||
margin: base(.5);
|
||||
max-width: initial;
|
||||
}
|
||||
|
||||
@@ -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> = (props) => {
|
||||
<ul className={baseClass}>
|
||||
{docs.map((doc) => (
|
||||
<li key={String(doc.id)}>
|
||||
<UploadCard
|
||||
<ThumbnailCard
|
||||
doc={doc}
|
||||
{...{ collection }}
|
||||
collection={collection}
|
||||
onClick={() => onCardClick(doc)}
|
||||
/>
|
||||
</li>
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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> = (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 (
|
||||
<button
|
||||
className={baseClass}
|
||||
tabIndex={0}
|
||||
type="button"
|
||||
onClick={handleBlockSelection}
|
||||
>
|
||||
<div className={`${baseClass}__image`}>
|
||||
{imageURL ? (
|
||||
<img
|
||||
src={imageURL}
|
||||
alt={imageAltText}
|
||||
/>
|
||||
) : <DefaultBlockImage />}
|
||||
</div>
|
||||
<div className={`${baseClass}__label`}>{getTranslation(labels.singular, i18n)}</div>
|
||||
</button>
|
||||
);
|
||||
};
|
||||
|
||||
export default BlockSelection;
|
||||
@@ -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
|
||||
}
|
||||
@@ -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)});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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> = (props) => {
|
||||
|
||||
return (
|
||||
<Drawer slug={drawerSlug}>
|
||||
<Gutter className={baseClass}>
|
||||
<Gutter className={`${baseClass}__wrapper`}>
|
||||
<h2>
|
||||
{t('addLabel', { label: getTranslation(labels.singular, i18n) })}
|
||||
</h2>
|
||||
<BlockSearch setSearchTerm={setSearchTerm} />
|
||||
<div className={baseClass}>
|
||||
{filteredBlocks?.map((block, index) => (
|
||||
<BlockSelection
|
||||
key={index}
|
||||
block={block}
|
||||
onClick={() => {
|
||||
closeModal(drawerSlug);
|
||||
}}
|
||||
addRow={addRow}
|
||||
addRowIndex={addRowIndex}
|
||||
/>
|
||||
))}
|
||||
<div className={`${baseClass}__blocks-wrapper`}>
|
||||
<ul className={`${baseClass}__blocks`}>
|
||||
{filteredBlocks?.map((block, index) => {
|
||||
const {
|
||||
labels: blockLabels,
|
||||
slug,
|
||||
imageURL,
|
||||
imageAltText,
|
||||
} = block;
|
||||
|
||||
return (
|
||||
<li
|
||||
key={index}
|
||||
className={`${baseClass}__block`}
|
||||
>
|
||||
<ThumbnailCard
|
||||
onClick={() => {
|
||||
addRow(addRowIndex, slug);
|
||||
closeModal(drawerSlug);
|
||||
}}
|
||||
thumbnail={imageURL ? (
|
||||
<img
|
||||
src={imageURL}
|
||||
alt={imageAltText}
|
||||
/>
|
||||
) : (
|
||||
<div className={`${baseClass}__default-image`}>
|
||||
<DefaultBlockImage />
|
||||
</div>
|
||||
)}
|
||||
label={getTranslation(blockLabels.singular, i18n)}
|
||||
alignLabel="center"
|
||||
/>
|
||||
</li>
|
||||
);
|
||||
})}
|
||||
</ul>
|
||||
</div>
|
||||
</Gutter>
|
||||
</Drawer>
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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 () => {
|
||||
|
||||
@@ -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');
|
||||
|
||||
Reference in New Issue
Block a user