feat: 2.0 popover style updates (#3404)

This commit is contained in:
Jarrod Flesch
2023-09-29 09:05:02 -04:00
committed by GitHub
parent f723b0e0e9
commit 6fa5866f99
23 changed files with 400 additions and 460 deletions

View File

@@ -11,16 +11,6 @@
}
}
&.popup--active .array-actions__button {
background: var(--theme-elevation-0);
}
&__button,
&__action {
@extend %btn-reset;
cursor: pointer;
}
&__actions {
list-style: none;
margin: 0;
@@ -28,9 +18,6 @@
}
&__action {
@extend %btn-reset;
display: block;
svg {
position: relative;
top: -1px;
@@ -40,10 +27,6 @@
stroke-width: 1px;
}
}
&:hover {
opacity: 0.7;
}
}
&__move-up {

View File

@@ -9,6 +9,7 @@ import More from '../../icons/More'
import Plus from '../../icons/Plus'
import X from '../../icons/X'
import Popup from '../Popup'
import * as PopupList from '../Popup/PopupButtonList'
import './index.scss'
const baseClass = 'array-actions'
@@ -31,73 +32,69 @@ export const ArrayAction: React.FC<Props> = ({
horizontalAlign="center"
render={({ close }) => {
return (
<React.Fragment>
<PopupList.ButtonGroup buttonSize="small">
{index !== 0 && (
<button
<PopupList.Button
className={`${baseClass}__action ${baseClass}__move-up`}
onClick={() => {
moveRow(index, index - 1)
close()
}}
type="button"
>
<Chevron />
{t('moveUp')}
</button>
</PopupList.Button>
)}
{index < rowCount - 1 && (
<button
className={`${baseClass}__action ${baseClass}__move-down`}
<PopupList.Button
className={`${baseClass}__action`}
onClick={() => {
moveRow(index, index + 1)
close()
}}
type="button"
>
<Chevron />
{t('moveDown')}
</button>
</PopupList.Button>
)}
{!hasMaxRows && (
<React.Fragment>
<button
<PopupList.Button
className={`${baseClass}__action ${baseClass}__add`}
onClick={() => {
addRow(index + 1)
close()
}}
type="button"
>
<Plus />
{t('addBelow')}
</button>
<button
</PopupList.Button>
<PopupList.Button
className={`${baseClass}__action ${baseClass}__duplicate`}
onClick={() => {
duplicateRow(index)
close()
}}
type="button"
>
<Copy />
{t('duplicate')}
</button>
</PopupList.Button>
</React.Fragment>
)}
<button
<PopupList.Button
className={`${baseClass}__action ${baseClass}__remove`}
onClick={() => {
removeRow(index)
close()
}}
type="button"
>
<X />
{t('remove')}
</button>
</React.Fragment>
</PopupList.Button>
</PopupList.ButtonGroup>
)
}}
size="medium"
/>
)
}

View File

@@ -73,13 +73,14 @@
}
&__controls-wrapper {
--controls-gap: calc(var(--base) / 2);
--dot-button-width: calc(var(--base) * 2);
display: flex;
align-items: center;
margin: 0;
// move to the right to account for the padding on the dots
// this will make sure the alignment is correct
// while still keeping a large button hitbox
transform: translate3d(var(--base), 0, 0);
gap: var(--controls-gap);
padding-right: calc(var(--controls-gap) + var(--dot-button-width));
position: relative;
}
&__controls {
@@ -94,27 +95,20 @@
}
}
&__popup {
.popup-button {
padding: var(--base);
background: transparent;
border: none;
cursor: pointer;
color: var(--theme-elevation-500);
&:hover {
color: var(--theme-text);
}
}
}
&__dots {
display: flex;
gap: 2px;
background-color: transparent;
padding: 0;
height: 100%;
margin: 0;
> div {
.btn__label {
height: 100%;
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
gap: 2px;
}
> span > span > div {
width: 3px;
height: 3px;
border-radius: 100%;
@@ -122,49 +116,15 @@
}
}
&__popup-actions {
list-style: none;
padding: 0;
margin: 0;
display: flex;
flex-direction: column;
gap: calc(var(--base) / 4);
&__popup {
position: absolute;
right: 0;
top: 0;
bottom: 0;
width: var(--dot-button-width);
a {
text-decoration: none;
}
li {
position: relative;
&::before {
content: '';
display: block;
position: absolute;
height: 100%;
width: 100%;
border-radius: 1px;
background: var(--theme-elevation-100);
width: calc(100% + (var(--base) / 2));
left: calc(var(--base) / -4);
top: 0;
opacity: 0;
transition: opacity 50ms linear;
}
&:hover::before {
opacity: 1;
}
> * {
position: relative;
width: 100%;
height: 100%;
text-align: left;
font-size: inherit;
line-height: inherit;
font-family: inherit;
}
.popup__trigger-wrap {
height: 100%;
}
}

View File

@@ -1,6 +1,5 @@
import React, { Fragment } from 'react'
import { useTranslation } from 'react-i18next'
import { Link } from 'react-router-dom'
import type { CollectionPermission, GlobalPermission } from '../../../../auth'
import type { SanitizedCollectionConfig, SanitizedGlobalConfig } from '../../../../exports/types'
@@ -9,10 +8,12 @@ import { formatDate } from '../../../utilities/formatDate'
import { useConfig } from '../../utilities/Config'
import { useDocumentInfo } from '../../utilities/DocumentInfo'
import Autosave from '../Autosave'
import Button from '../Button'
import DeleteDocument from '../DeleteDocument'
import DuplicateDocument from '../DuplicateDocument'
import { Gutter } from '../Gutter'
import Popup from '../Popup'
import * as PopupList from '../Popup/PopupButtonList'
import PreviewButton from '../PreviewButton'
import { Publish } from '../Publish'
import { Save } from '../Save'
@@ -30,9 +31,9 @@ export const DocumentControls: React.FC<{
global?: SanitizedGlobalConfig
hasSavePermission?: boolean
id?: string
isAccountView?: boolean
isEditing?: boolean
permissions?: CollectionPermission | GlobalPermission
isAccountView?: boolean
}> = (props) => {
const {
id,
@@ -41,9 +42,9 @@ export const DocumentControls: React.FC<{
disableActions,
global,
hasSavePermission,
isAccountView,
isEditing,
permissions,
isAccountView,
} = props
const { publishedDoc } = useDocumentInfo()
@@ -73,6 +74,8 @@ export const DocumentControls: React.FC<{
!global?.versions?.drafts?.autosave
}
const showDotMenu = Boolean(collection && !disableActions)
return (
<Gutter className={baseClass}>
<div className={`${baseClass}__wrapper`}>
@@ -187,49 +190,47 @@ export const DocumentControls: React.FC<{
</React.Fragment>
)}
</div>
{Boolean(collection && !disableActions) && (
{showDotMenu && (
<Popup
button={
<div className={`${baseClass}__dots`}>
<Button buttonStyle="secondary" className={`${baseClass}__dots`} el="div">
<div />
<div />
<div />
</div>
</Button>
}
caret={false}
className={`${baseClass}__popup`}
horizontalAlign="center"
horizontalAlign="right"
size="large"
verticalAlign="bottom"
>
<ul className={`${baseClass}__popup-actions`}>
<PopupList.ButtonGroup>
{'create' in permissions && permissions?.create?.permission && (
<React.Fragment>
<li>
<Link
id="action-create"
to={`${adminRoute}/collections/${collection?.slug}/create`}
>
{t('createNew')}
</Link>
</li>
<PopupList.Button
id="action-create"
to={`${adminRoute}/collections/${collection?.slug}/create`}
>
{t('createNew')}
</PopupList.Button>
{!collection?.admin?.disableDuplicate && isEditing && (
<li>
<PopupList.Button>
<DuplicateDocument
collection={collection}
id={id}
slug={collection?.slug}
/>
</li>
</PopupList.Button>
)}
</React.Fragment>
)}
{'delete' in permissions && permissions?.delete?.permission && id && (
<li>
<PopupList.Button>
<DeleteDocument buttonId="action-delete" collection={collection} id={id} />
</li>
</PopupList.Button>
)}
</ul>
</PopupList.ButtonGroup>
</Popup>
)}
</div>

View File

@@ -18,7 +18,7 @@
button {
color: currentColor;
padding: base(0.25) 0;
padding: 0;
font-size: 1rem;
line-height: base(1);
background: transparent;
@@ -39,33 +39,10 @@
&__button {
white-space: nowrap;
display: flex;
}
span {
color: var(--theme-elevation-400);
}
ul {
list-style: none;
padding: 0;
max-height: base(8);
margin: 0;
li a {
all: unset;
cursor: pointer;
padding-right: 0;
&:hover,
&:focus-visible {
text-decoration: underline;
}
}
}
@include mid-break {
.popup__content {
width: calc(100vw - calc(var(--gutter-h) * 2));
}
}
}

View File

@@ -1,13 +1,13 @@
import qs from 'qs'
import React from 'react'
import { useTranslation } from 'react-i18next'
import { Link } from 'react-router-dom'
import { Chevron } from '../..'
import { useConfig } from '../../utilities/Config'
import { useLocale } from '../../utilities/Locale'
import { useSearchParams } from '../../utilities/SearchParams'
import Popup from '../Popup'
import * as PopupList from '../Popup/PopupButtonList'
import './index.scss'
const baseClass = 'localizer'
@@ -37,20 +37,10 @@ const Localizer: React.FC<{
<Chevron className={`${baseClass}__chevron`} />
</div>
}
caret={false}
horizontalAlign="left"
horizontalAlign="right"
render={({ close }) => (
<ul>
<PopupList.ButtonGroup>
{locales.map((localeOption) => {
const baseLocaleClass = `${baseClass}__locale`
const localeClasses = [
baseLocaleClass,
locale.code === localeOption.code && `${baseLocaleClass}--active`,
]
.filter(Boolean)
.join('')
const newParams = {
...searchParams,
locale: localeOption.code,
@@ -60,20 +50,19 @@ const Localizer: React.FC<{
if (localeOption.code !== locale.code) {
return (
<li className={localeClasses} key={localeOption.code}>
<Link onClick={close} to={{ search }}>
{localeOption.label}
{localeOption.label !== localeOption.code && ` (${localeOption.code})`}
</Link>
</li>
<PopupList.Button key={localeOption.code} onClick={close} to={{ search }}>
{localeOption.label}
{localeOption.label !== localeOption.code && ` (${localeOption.code})`}
</PopupList.Button>
)
}
return null
})}
</ul>
</PopupList.ButtonGroup>
)}
showScrollbar
size="large"
/>
</div>
)

View File

@@ -7,12 +7,6 @@
margin: 0;
}
.popup-button--default {
@extend %btn-reset;
position: relative;
cursor: pointer;
}
&__button {
@extend %btn-reset;
cursor: pointer;

View File

@@ -7,6 +7,7 @@ import { defaults } from '../../../../collections/config/defaults'
import Chevron from '../../icons/Chevron'
import { useSearchParams } from '../../utilities/SearchParams'
import Popup from '../Popup'
import * as PopupList from '../Popup/PopupButtonList'
import './index.scss'
const baseClass = 'per-page'
@@ -43,43 +44,37 @@ const PerPage: React.FC<Props> = ({
}
horizontalAlign="right"
render={({ close }) => (
<div>
<ul>
{limits.map((limitNumber, i) => (
<li className={`${baseClass}-item`} key={i}>
<button
className={[
`${baseClass}__button`,
limitNumber === Number(limit) && `${baseClass}__button-active`,
]
.filter(Boolean)
.join(' ')}
onClick={() => {
close()
if (handleChange) handleChange(limitNumber)
if (modifySearchParams) {
history.replace({
search: qs.stringify(
{
...params,
limit: limitNumber,
page: resetPage ? 1 : params.page,
},
{ addQueryPrefix: true },
),
})
}
}}
type="button"
>
{limitNumber === Number(limit) && <Chevron />}
{limitNumber}
</button>
</li>
))}
</ul>
</div>
<PopupList.ButtonGroup>
{limits.map((limitNumber, i) => (
<PopupList.Button
className={[limitNumber === Number(limit) && `${baseClass}__button-active`]
.filter(Boolean)
.join(' ')}
key={i}
onClick={() => {
close()
if (handleChange) handleChange(limitNumber)
if (modifySearchParams) {
history.replace({
search: qs.stringify(
{
...params,
limit: limitNumber,
page: resetPage ? 1 : params.page,
},
{ addQueryPrefix: true },
),
})
}
}}
>
{limitNumber === Number(limit) && <Chevron />}
{limitNumber}
</PopupList.Button>
))}
</PopupList.ButtonGroup>
)}
size="small"
/>
</div>
)

View File

@@ -1,5 +0,0 @@
@import '../../../../scss/styles.scss';
.popup-button {
display: inline-flex;
}

View File

@@ -0,0 +1,47 @@
@import '../../../../scss/styles.scss';
.popup-button-list {
display: flex;
flex-direction: column;
text-align: left;
--list-button-padding: calc(var(--base) * 0.5);
&__text-align--left {
text-align: left;
}
&__text-align--center {
text-align: center;
}
&__text-align--right {
text-align: right;
}
&__button {
@extend %btn-reset;
padding-left: var(--list-button-padding);
padding-right: var(--list-button-padding);
padding-top: 2px;
padding-bottom: 2px;
cursor: pointer;
text-align: inherit;
line-height: var(--base);
text-decoration: none;
border-radius: 3px;
button {
@extend %btn-reset;
&:focus-visible {
outline: none;
}
}
&:hover,
&:focus-visible,
&:focus-within {
background-color: var(--popup-button-highlight);
}
}
}

View File

@@ -0,0 +1,75 @@
import type { LinkProps } from 'react-router-dom'
import * as React from 'react'
import { Link } from 'react-router-dom'
import './index.scss'
const baseClass = 'popup-button-list'
export const ButtonGroup: React.FC<{
buttonSize?: 'default' | 'small'
children: React.ReactNode
className?: string
textAlign?: 'center' | 'left' | 'right'
}> = ({ buttonSize = 'default', children, className, textAlign = 'left' }) => {
const classes = [
baseClass,
className,
`${baseClass}__text-align--${textAlign}`,
`${baseClass}__button-size--${buttonSize}`,
]
.filter(Boolean)
.join(' ')
return <div className={classes}>{children}</div>
}
type MenuButtonProps = {
children: React.ReactNode
className?: string
id?: string
onClick?: () => void
to?: LinkProps['to']
}
export const Button: React.FC<MenuButtonProps> = ({ id, children, className, onClick, to }) => {
const classes = [`${baseClass}__button`, className].filter(Boolean).join(' ')
if (to) {
return (
<Link
className={classes}
id={id}
onClick={() => {
if (onClick) {
onClick()
}
}}
to={to}
>
{children}
</Link>
)
}
if (onClick) {
return (
<button
className={classes}
id={id}
onClick={() => {
if (onClick) {
onClick()
}
}}
type="button"
>
{children}
</button>
)
}
return (
<div className={classes} id={id}>
{children}
</div>
)
}

View File

@@ -0,0 +1,14 @@
@import '../../../../scss/styles.scss';
.popup-button {
height: 100%;
color: currentColor;
padding: 0;
font-size: 1rem;
line-height: base(1);
background: transparent;
border: 0;
font-weight: 600;
cursor: pointer;
display: inline-flex;
}

View File

@@ -6,7 +6,7 @@ import './index.scss'
const baseClass = 'popup-button'
const PopupButton: React.FC<Props> = (props) => {
export const PopupTrigger: React.FC<Props> = (props) => {
const { active, button, buttonType, className, setActive } = props
const classes = [baseClass, className, `${baseClass}--${buttonType}`].filter(Boolean).join(' ')
@@ -41,5 +41,3 @@ const PopupButton: React.FC<Props> = (props) => {
</button>
)
}
export default PopupButton

View File

@@ -1,43 +1,48 @@
@import '../../../scss/styles.scss';
.popup {
--popup-button-highlight: var(--theme-elevation-200);
--popup-bg: var(--theme-input-bg);
--popup-text: var(--theme-text);
--popup-caret-size: 10px;
--popup-x-padding: calc(var(--base) * 0.33);
--popup-padding: calc(var(--base) * 0.5);
position: relative;
&__content {
position: absolute;
background: var(--theme-input-bg);
background: var(--popup-bg);
opacity: 0;
visibility: hidden;
pointer-events: none;
z-index: var(--z-popup);
max-width: calc(100vw - #{$baseline});
&--caret {
&:after {
content: ' ';
position: absolute;
top: calc(100% - 1px);
border: 12px solid transparent;
border-top-color: var(--theme-input-bg);
}
}
color: var(--popup-text);
border-radius: 4px;
padding-left: var(--popup-padding);
padding-right: var(--popup-padding);
min-width: var(--popup-width, auto);
}
&__wrap {
&__hide-scrollbar {
overflow: hidden;
}
.popup__scroll {
padding: $baseline;
&__scroll-container {
overflow-y: auto;
white-space: nowrap;
padding-right: calc(var(--scrollbar-width) + #{$baseline});
width: calc(100% + var(--scrollbar-width));
padding-top: var(--popup-padding);
padding-bottom: var(--popup-padding);
}
&__scroll-content {
width: calc(100% - var(--scrollbar-width));
}
&--show-scrollbar {
.popup__scroll {
padding-right: 0;
.popup__scroll-container,
.popup__scroll-content {
width: 100%;
}
}
@@ -52,63 +57,24 @@
////////////////////////////////
&--size-small {
.popup__scroll {
[dir='ltr'] & {
padding: base(0.75) calc(var(--scrollbar-width) + #{base(0.75)}) base(0.75) base(0.75);
}
[dir='rtl'] & {
padding: base(0.75) base(0.75) base(0.75) calc(var(--scrollbar-width) + #{base(0.75)});
}
}
--popup-width: 100px;
.popup__content {
@include shadow-m;
}
}
&.popup--h-align-left {
.popup__content {
left: - base(0.5);
&:after {
left: base(0.425);
}
}
&--size-medium {
--popup-width: 150px;
.popup__content {
@include shadow-lg;
}
}
&--size-large {
--popup-width: 200px;
.popup__content {
@include shadow-lg;
}
.popup__scroll {
padding: base(1) calc(var(--scrollbar-width) + #{base(1.5)}) base(1) base(1.5);
}
}
&--size-wide {
.popup__content {
@include shadow-m;
&:after {
border: 12px solid transparent;
border-top-color: var(--theme-input-bg);
}
}
.popup__scroll {
padding: base(0.25) base(0.5);
}
&.popup--align-left {
.popup__content {
left: - base(0.5);
&:after {
left: base(0.425);
}
}
}
}
////////////////////////////////
@@ -116,12 +82,8 @@
////////////////////////////////
&--h-align-left {
.popup__content {
left: - base(1.75);
&:after {
left: base(1.75);
}
.popup__caret {
left: var(--popup-padding);
}
}
@@ -129,26 +91,21 @@
.popup__content {
left: 50%;
transform: translateX(-50%);
}
&:after {
left: 50%;
transform: translateX(-50%);
}
.popup__caret {
left: 50%;
transform: translateX(-50%);
}
}
&--h-align-right {
.popup__content {
right: - base(1.75);
[dir='rtl'] & {
right: - base(0.75);
}
&:after {
right: base(1.75);
[dir='rtl'] & {
right: base(0.75);
}
}
right: 0;
}
.popup__caret {
right: var(--popup-padding);
}
}
@@ -156,46 +113,32 @@
// VERTICAL ALIGNMENT
////////////////////////////////
&__caret {
position: absolute;
border: var(--popup-caret-size) solid transparent;
}
&--v-align-top {
.popup__content {
bottom: calc(100% + #{$baseline});
@include shadow-lg;
bottom: calc(100% + var(--popup-caret-size));
}
.popup__caret {
top: calc(100% - 1px);
border-top-color: var(--popup-bg);
}
}
&--v-align-bottom {
.popup__content {
@include shadow-lg-top;
top: calc(100% + #{base(0.5)});
&:after {
top: unset;
bottom: calc(100% - 1px);
border-top-color: transparent !important;
border-bottom-color: var(--theme-input-bg);
}
top: calc(100% + var(--popup-caret-size));
}
&.popup--color-dark {
.popup__content {
&:after {
border-bottom-color: var(--theme-elevation-800);
}
}
}
}
////////////////////////////////
// COLOR
////////////////////////////////
&--color-dark {
.popup__content {
background: var(--theme-elevation-800);
color: var(--theme-input-bg);
&:after {
border-top-color: var(--theme-elevation-800);
}
.popup__caret {
bottom: calc(100% - 1px);
border-bottom-color: var(--popup-bg);
}
}
@@ -212,69 +155,55 @@
}
@include mid-break {
&__scroll,
&--size-large .popup__scroll {
padding: base(0.75);
padding-right: calc(var(--scrollbar-width) + #{base(0.75)});
}
&--h-align-left {
.popup__content {
left: - base(0.5);
&:after {
left: base(0.5);
}
}
}
--popup-padding: calc(var(--base) * 0.25);
&--h-align-center {
.popup__content {
left: 50%;
transform: translateX(-0%);
}
&:after {
left: 50%;
transform: translateX(-0%);
}
.popup__caret {
left: 50%;
transform: translateX(-0%);
}
}
&--h-align-right {
.popup__content {
right: - base(0.5);
right: 0;
}
&:after {
right: base(0.5);
}
.popup__caret {
right: var(--popup-padding);
}
}
&--force-h-align-left {
.popup__content {
left: - base(0.5);
left: 0;
right: unset;
transform: unset;
}
&:after {
left: base(0.5);
right: unset;
transform: unset;
}
.popup__caret {
left: var(--popup-padding);
right: unset;
transform: unset;
}
}
&--force-h-align-right {
.popup__content {
right: - base(0.5);
right: 0;
left: unset;
transform: unset;
}
&:after {
right: base(0.5);
left: unset;
transform: unset;
}
.popup__caret {
right: var(--popup-padding);
left: unset;
transform: unset;
}
}
}

View File

@@ -4,7 +4,7 @@ import React, { useCallback, useEffect, useRef, useState } from 'react'
import type { Props } from './types'
import useIntersect from '../../../hooks/useIntersect'
import PopupButton from './PopupButton'
import { PopupTrigger } from './PopupTrigger'
import './index.scss'
const baseClass = 'popup'
@@ -18,16 +18,14 @@ const Popup: React.FC<Props> = (props) => {
caret = true,
children,
className,
color = 'light',
forceOpen,
horizontalAlign: horizontalAlignFromProps = 'left',
initActive = false,
onToggleOpen,
padding,
render,
showOnHover = false,
showScrollbar = false,
size = 'small',
size = 'medium',
verticalAlign: verticalAlignFromProps = 'top',
} = props
@@ -38,7 +36,6 @@ const Popup: React.FC<Props> = (props) => {
threshold: 1,
})
const buttonRef = useRef(null)
const contentRef = useRef(null)
const [active, setActive] = useState(initActive)
const [verticalAlign, setVerticalAlign] = useState(verticalAlignFromProps)
@@ -57,8 +54,8 @@ const Popup: React.FC<Props> = (props) => {
} = bounds
let boundingTopPos = 100
let boundingRightPos = window.innerWidth
let boundingBottomPos = window.innerHeight
let boundingRightPos = document.documentElement.clientWidth
let boundingBottomPos = document.documentElement.clientHeight
let boundingLeftPos = 0
if (boundingRef?.current) {
@@ -131,7 +128,6 @@ const Popup: React.FC<Props> = (props) => {
baseClass,
className,
`${baseClass}--size-${size}`,
`${baseClass}--color-${color}`,
`${baseClass}--v-align-${verticalAlign}`,
`${baseClass}--h-align-${horizontalAlign}`,
active && `${baseClass}--active`,
@@ -142,39 +138,35 @@ const Popup: React.FC<Props> = (props) => {
return (
<div className={classes}>
<div className={`${baseClass}__wrapper`} ref={buttonRef}>
<div className={`${baseClass}__trigger-wrap`}>
{showOnHover ? (
<div
className={`${baseClass}__on-hover-watch`}
onMouseEnter={() => setActive(true)}
onMouseLeave={() => setActive(false)}
>
<PopupButton
<PopupTrigger
{...{ active, button, buttonType, className: buttonClassName, setActive }}
/>
</div>
) : (
<PopupButton {...{ active, button, buttonType, className: buttonClassName, setActive }} />
<PopupTrigger
{...{ active, button, buttonType, className: buttonClassName, setActive }}
/>
)}
</div>
<div
className={[`${baseClass}__content`, caret && `${baseClass}__content--caret`]
.filter(Boolean)
.join(' ')}
ref={contentRef}
>
<div className={`${baseClass}__wrap`} ref={intersectionRef}>
<div
className={`${baseClass}__scroll`}
style={{
padding,
}}
>
{render && render({ close: () => setActive(false) })}
{children && children}
<div className={`${baseClass}__content`} ref={contentRef}>
<div className={`${baseClass}__hide-scrollbar`} ref={intersectionRef}>
<div className={`${baseClass}__scroll-container`}>
<div className={`${baseClass}__scroll-content`}>
{render && render({ close: () => setActive(false) })}
{children && children}
</div>
</div>
</div>
{caret && <div className={`${baseClass}__caret`} />}
</div>
</div>
)

View File

@@ -9,15 +9,13 @@ export type Props = {
caret?: boolean
children?: React.ReactNode
className?: string
color?: 'dark' | 'light'
forceOpen?: boolean
horizontalAlign?: 'center' | 'left' | 'right'
initActive?: boolean
onToggleOpen?: (active: boolean) => void
padding?: CSSProperties['padding']
render?: (any) => React.ReactNode
showOnHover?: boolean
showScrollbar?: boolean
size?: 'large' | 'small' | 'wide'
size?: 'fit-content' | 'large' | 'medium' | 'small'
verticalAlign?: 'bottom' | 'top'
}

View File

@@ -4,7 +4,7 @@
display: flex;
align-items: stretch;
.popup__wrapper {
.popup__trigger-wrap {
display: flex;
align-items: stretch;
height: 100%;
@@ -22,27 +22,4 @@
display: flex;
cursor: pointer;
}
&__relations {
list-style: none;
margin: 0;
padding: 0;
li:not(:last-child) {
margin-bottom: base(0.375);
}
}
&__relation-button {
@extend %btn-reset;
cursor: pointer;
display: block;
padding: base(0.125) 0;
text-align: center;
width: 100%;
&:hover {
opacity: 0.7;
}
}
}

View File

@@ -2,6 +2,7 @@ import React, { Fragment, useCallback, useEffect, useState } from 'react'
import { useTranslation } from 'react-i18next'
import type { SanitizedCollectionConfig } from '../../../../../../collections/config/types'
import type { EditViewProps } from '../../../../views/types'
import type { Value } from '../types'
import type { Props } from './types'
@@ -9,13 +10,13 @@ import { getTranslation } from '../../../../../../utilities/getTranslation'
import Button from '../../../../elements/Button'
import { useDocumentDrawer } from '../../../../elements/DocumentDrawer'
import Popup from '../../../../elements/Popup'
import * as PopupList from '../../../../elements/Popup/PopupButtonList'
import Tooltip from '../../../../elements/Tooltip'
import Plus from '../../../../icons/Plus'
import { useAuth } from '../../../../utilities/Auth'
import { useConfig } from '../../../../utilities/Config'
import './index.scss'
import { useRelatedCollections } from './useRelatedCollections'
import { EditViewProps } from '../../../../views/types'
const baseClass = 'relationship-add-new'
@@ -85,7 +86,7 @@ export const AddNewRelation: React.FC<Props> = ({
[relationTo, collectionConfig, dispatchOptions, i18n, hasMany, setValue, value, config],
)
const onPopopToggle = useCallback((state) => {
const onPopupToggle = useCallback((state) => {
setPopupOpen(state)
}, [])
@@ -161,31 +162,30 @@ export const AddNewRelation: React.FC<Props> = ({
}
buttonType="custom"
horizontalAlign="center"
onToggleOpen={onPopopToggle}
onToggleOpen={onPopupToggle}
render={({ close: closePopup }) => (
<ul className={`${baseClass}__relations`}>
<PopupList.ButtonGroup>
{relatedCollections.map((relatedCollection) => {
if (permissions.collections[relatedCollection.slug].create.permission) {
return (
<li key={relatedCollection.slug}>
<button
className={`${baseClass}__relation-button ${baseClass}__relation-button--${relatedCollection.slug}`}
onClick={() => {
closePopup()
setSelectedCollection(relatedCollection.slug)
}}
type="button"
>
{getTranslation(relatedCollection.labels.singular, i18n)}
</button>
</li>
<PopupList.Button
className={`${baseClass}__relation-button--${relatedCollection.slug}`}
key={relatedCollection.slug}
onClick={() => {
closePopup()
setSelectedCollection(relatedCollection.slug)
}}
>
{getTranslation(relatedCollection.labels.singular, i18n)}
</PopupList.Button>
)
}
return null
})}
</ul>
</PopupList.ButtonGroup>
)}
size="medium"
/>
{collectionConfig &&
permissions.collections[collectionConfig.slug].create.permission && (

View File

@@ -85,8 +85,8 @@ $focus-box-shadow: 0 0 0 $style-stroke-width-m var(--theme-success-500);
@mixin shadow-lg-top {
box-shadow:
0 -2px 20px 7px rgba(0, 2, 4, 0.1),
0 6px 4px -4px rgba(0, 2, 4, 0.02);
0 -20px 35px -10px rgba(0, 2, 4, 0.2),
0 -6px 4px -4px rgba(0, 2, 4, 0.02);
}
@mixin shadow {

View File

@@ -11,14 +11,13 @@
bottom: 0;
left: 0;
.popup__scroll,
.popup__wrap {
.popup__hide-scrollbar,
.popup__scroll-container {
overflow: visible;
}
.popup__scroll {
.popup__scroll-content {
white-space: pre;
padding-right: base(0.5);
}
}
@@ -35,7 +34,7 @@
@extend %btn-reset;
font-weight: 600;
cursor: pointer;
margin: 0 0 0 base(0.25);
margin: 0;
&:hover,
&:focus-visible {
@@ -48,7 +47,29 @@
max-width: base(8);
overflow: hidden;
text-overflow: ellipsis;
margin-right: base(0.25);
border-radius: 2px;
&:hover {
background-color: var(--popup-button-highlight);
}
}
}
.rich-text-link__popup {
display: flex;
gap: calc(var(--base) * 0.25);
button {
&:hover {
.btn__icon {
background-color: var(--popup-button-highlight);
.fill {
fill: var(--theme-text);
}
.stroke {
stroke: var(--theme-text);
}
}
}
}
}

View File

@@ -159,6 +159,7 @@ export const LinkElement: React.FC<{
href={`${config.routes.admin}/collections/${element.doc.relationTo}/${element.doc.value}`}
rel="noreferrer"
target="_blank"
title={`${config.routes.admin}/collections/${element.doc.relationTo}/${element.doc.value}`}
>
label
</a>
@@ -170,6 +171,7 @@ export const LinkElement: React.FC<{
href={element.url}
rel="noreferrer"
target="_blank"
title={element.url}
>
{element.url}
</a>
@@ -200,7 +202,7 @@ export const LinkElement: React.FC<{
/>
</div>
)}
size="small"
size="fit-content"
verticalAlign="bottom"
/>
</span>

View File

@@ -321,13 +321,13 @@ describe('fields', () => {
const firstBlockSelector = blocksDrawer
.locator('.blocks-drawer__blocks .blocks-drawer__block')
.first()
await expect(firstBlockSelector).toContainText('Text')
await expect(firstBlockSelector).toContainText('Content')
await firstBlockSelector.click()
// ensure the block was appended to the rows
const addedRow = page.locator('#field-blocks .blocks-field__row').last()
await expect(addedRow).toBeVisible()
await expect(addedRow.locator('.blocks-field__block-pill-text')).toContainText('Text')
await expect(addedRow.locator('.blocks-field__block-pill-content')).toContainText('Content')
})
test('should open blocks drawer from block row and add below', async () => {
@@ -348,13 +348,13 @@ describe('fields', () => {
const firstBlockSelector = blocksDrawer
.locator('.blocks-drawer__blocks .blocks-drawer__block')
.first()
await expect(firstBlockSelector).toContainText('Text')
await expect(firstBlockSelector).toContainText('Content')
await firstBlockSelector.click()
// ensure the block was inserted beneath the first in the rows
const addedRow = page.locator('#field-blocks #blocks-row-1')
await expect(addedRow).toBeVisible()
await expect(addedRow.locator('.blocks-field__block-pill-text')).toContainText('Text') // went from `Number` to `Text`
await expect(addedRow.locator('.blocks-field__block-pill-content')).toContainText('Content') // went from `Number` to `Content`
})
test('should use i18n block labels', async () => {
@@ -488,18 +488,16 @@ describe('fields', () => {
await page.locator('#potentiallyEmptyArray-row-1 .array-actions__button').click()
await page
.locator('#potentiallyEmptyArray-row-1 .popup__scroll .array-actions__remove')
.locator('#potentiallyEmptyArray-row-1 .popup__scroll-container .array-actions__remove')
.click()
await page.locator('#potentiallyEmptyArray-row-0 .array-actions__button').click()
await page
.locator('#potentiallyEmptyArray-row-0 .popup__scroll .array-actions__remove')
.locator('#potentiallyEmptyArray-row-0 .popup__scroll-container .array-actions__remove')
.click()
const rows = await page.locator(
'#field-potentiallyEmptyArray > .array-field__draggable-rows',
)
const rows = page.locator('#field-potentiallyEmptyArray > .array-field__draggable-rows')
expect(rows).not.toBeVisible()
await expect(rows).toBeHidden()
})
test('should remove existing row', async () => {
@@ -513,15 +511,13 @@ describe('fields', () => {
await page.locator('#potentiallyEmptyArray-row-0 .array-actions__button').click()
await page
.locator(
'#potentiallyEmptyArray-row-0 .popup__scroll .array-actions__action.array-actions__remove',
'#potentiallyEmptyArray-row-0 .popup__scroll-container .array-actions__action.array-actions__remove',
)
.click()
const rows = await page.locator(
'#field-potentiallyEmptyArray > .array-field__draggable-rows',
)
const rows = page.locator('#field-potentiallyEmptyArray > .array-field__draggable-rows')
expect(rows).not.toBeVisible()
await expect(rows).toBeHidden()
})
test('should add row after removing existing row', async () => {
@@ -537,7 +533,7 @@ describe('fields', () => {
await page.locator('#potentiallyEmptyArray-row-1 .array-actions__button').click()
await page
.locator(
'#potentiallyEmptyArray-row-1 .popup__scroll .array-actions__action.array-actions__remove',
'#potentiallyEmptyArray-row-1 .popup__scroll-container .array-actions__action.array-actions__remove',
)
.click()
await page.locator('#field-potentiallyEmptyArray > .array-field__add-row').click()