diff --git a/entry_types/scrolled/package/spec/frontend/appearance-spec.js b/entry_types/scrolled/package/spec/frontend/appearance-spec.js new file mode 100644 index 0000000000..68255ab521 --- /dev/null +++ b/entry_types/scrolled/package/spec/frontend/appearance-spec.js @@ -0,0 +1,139 @@ +import {renderHook} from '@testing-library/react-hooks'; + +import {useAppearanceOverlayStyle} from 'frontend/appearance'; + +describe('useAppearanceOverlayStyle', () => { + it('returns empty object for shadow appearance', () => { + const {result} = renderHook(() => + useAppearanceOverlayStyle({appearance: 'shadow'}) + ); + + expect(result.current).toEqual({}); + }); + + it('returns empty object for transparent appearance', () => { + const {result} = renderHook(() => + useAppearanceOverlayStyle({appearance: 'transparent'}) + ); + + expect(result.current).toEqual({}); + }); + + describe('cards appearance', () => { + it('returns backdropFilter for translucent cardSurfaceColor', () => { + const {result} = renderHook(() => + useAppearanceOverlayStyle({ + appearance: 'cards', + cardSurfaceColor: '#ff000080' + }) + ); + + expect(result.current).toEqual({ + backgroundColor: '#ff000080', + backdropFilter: 'blur(10px)' + }); + }); + + it('does not return backdropFilter for opaque cardSurfaceColor', () => { + const {result} = renderHook(() => + useAppearanceOverlayStyle({ + appearance: 'cards', + cardSurfaceColor: '#ff0000' + }) + ); + + expect(result.current).toEqual({ + backgroundColor: '#ff0000' + }); + }); + + it('does not return backdropFilter when no cardSurfaceColor is set', () => { + const {result} = renderHook(() => + useAppearanceOverlayStyle({appearance: 'cards'}) + ); + + expect(result.current).toEqual({}); + }); + + it('scales overlayBackdropBlur to max 10px', () => { + const {result} = renderHook(() => + useAppearanceOverlayStyle({ + appearance: 'cards', + cardSurfaceColor: '#ff000080', + overlayBackdropBlur: 50 + }) + ); + + expect(result.current).toEqual({ + backgroundColor: '#ff000080', + backdropFilter: 'blur(5px)' + }); + }); + + it('does not return backdropFilter when overlayBackdropBlur is 0', () => { + const {result} = renderHook(() => + useAppearanceOverlayStyle({ + appearance: 'cards', + cardSurfaceColor: '#ff000080', + overlayBackdropBlur: 0 + }) + ); + + expect(result.current).toEqual({ + backgroundColor: '#ff000080' + }); + }); + }); + + describe('split appearance', () => { + it('returns backdropFilter for translucent splitOverlayColor', () => { + const {result} = renderHook(() => + useAppearanceOverlayStyle({ + appearance: 'split', + splitOverlayColor: '#ff000080', + overlayBackdropBlur: 50 + }) + ); + + expect(result.current).toEqual({ + backgroundColor: '#ff000080', + backdropFilter: 'blur(5px)' + }); + }); + + it('returns default backdropFilter when no splitOverlayColor is set', () => { + const {result} = renderHook(() => + useAppearanceOverlayStyle({appearance: 'split'}) + ); + + expect(result.current).toEqual({backdropFilter: 'blur(10px)'}); + }); + + it('does not return backdropFilter for opaque splitOverlayColor', () => { + const {result} = renderHook(() => + useAppearanceOverlayStyle({ + appearance: 'split', + splitOverlayColor: '#ff0000' + }) + ); + + expect(result.current).toEqual({ + backgroundColor: '#ff0000' + }); + }); + + it('returns default backdropFilter for translucent splitOverlayColor', () => { + const {result} = renderHook(() => + useAppearanceOverlayStyle({ + appearance: 'split', + splitOverlayColor: '#ff000080' + }) + ); + + expect(result.current).toEqual({ + backgroundColor: '#ff000080', + backdropFilter: 'blur(10px)' + }); + }); + }); +}); diff --git a/entry_types/scrolled/package/spec/frontend/features/perElementFade-spec.js b/entry_types/scrolled/package/spec/frontend/features/perElementFade-spec.js new file mode 100644 index 0000000000..acf0caa257 --- /dev/null +++ b/entry_types/scrolled/package/spec/frontend/features/perElementFade-spec.js @@ -0,0 +1,37 @@ +import {renderEntry, usePageObjects} from 'support/pageObjects'; + +describe('fade transitions with backdrop blur', () => { + usePageObjects(); + + it('uses per-element fade to preserve backdrop blur', () => { + const {getSectionByPermaId} = renderEntry({ + seed: { + sections: [ + {id: 1, permaId: 9, configuration: {fullHeight: true, transition: 'scroll'}}, + {id: 2, permaId: 10, + configuration: {appearance: 'cards', cardSurfaceColor: '#ff000080', + fullHeight: true, transition: 'fade'}} + ], + contentElements: [{sectionId: 2}] + } + }); + + expect(getSectionByPermaId(10).usesPerElementFadeTransition()).toBe(true); + }); + + it('uses regular fade when there is no backdrop blur', () => { + const {getSectionByPermaId} = renderEntry({ + seed: { + sections: [ + {id: 1, permaId: 9, configuration: {fullHeight: true, transition: 'scroll'}}, + {id: 2, permaId: 10, + configuration: {appearance: 'cards', cardSurfaceColor: '#ff0000', + fullHeight: true, transition: 'fade'}} + ], + contentElements: [{sectionId: 2}] + } + }); + + expect(getSectionByPermaId(10).usesPerElementFadeTransition()).toBe(false); + }); +}); diff --git a/entry_types/scrolled/package/spec/frontend/foregroundBoxes/CardBoxWrapper-spec.js b/entry_types/scrolled/package/spec/frontend/foregroundBoxes/CardBoxWrapper-spec.js index 5e6ce49118..d6bb46dfe1 100644 --- a/entry_types/scrolled/package/spec/frontend/foregroundBoxes/CardBoxWrapper-spec.js +++ b/entry_types/scrolled/package/spec/frontend/foregroundBoxes/CardBoxWrapper-spec.js @@ -7,11 +7,14 @@ import CardBoxWrapper from 'frontend/foregroundBoxes/CardBoxWrapper'; import cardBoxStyles from 'frontend/foregroundBoxes/CardBoxWrapper.module.css'; import boundaryMarginStyles from 'frontend/foregroundBoxes/BoxBoundaryMargin.module.css'; +const transitionStyles = {foregroundOpacity: 'foregroundOpacity'}; + describe('CardBoxWrapper', () => { describe('at section boundaries', () => { it('does not have noTopMargin class when not at section start', () => { const {container} = render( - + Content ); @@ -21,7 +24,8 @@ describe('CardBoxWrapper', () => { it('has noTopMargin class when at section start', () => { const {container} = render( - + Content ); @@ -31,7 +35,8 @@ describe('CardBoxWrapper', () => { it('does not have noBottomMargin class when not at section end', () => { const {container} = render( - + Content ); @@ -41,7 +46,8 @@ describe('CardBoxWrapper', () => { it('has noBottomMargin class when at section end', () => { const {container} = render( - + Content ); @@ -50,88 +56,82 @@ describe('CardBoxWrapper', () => { }); }); - describe('backdrop blur', () => { - it('applies blur class when overlayBackdropBlur is set and color is translucent', () => { - const {container} = render( - - Content - - ); - - expect(container.firstChild).toHaveClass(cardBoxStyles.blur); - }); - - it('does not apply blur class when color is opaque', () => { - const {container} = render( - - Content - - ); - - expect(container.firstChild).not.toHaveClass(cardBoxStyles.blur); - }); - - it('does not apply blur when everything is default', () => { + describe('background element', () => { + it('applies foregroundOpacity class to background element', () => { const {container} = render( - + Content ); - expect(container.firstChild).not.toHaveClass(cardBoxStyles.blur); + expect(container.querySelector(`.${cardBoxStyles.cardBg}`)) + .toHaveClass('foregroundOpacity'); }); + }); - it('applies blur class by default for translucent color', () => { + describe('content wrapper', () => { + it('applies foregroundOpacity class to content wrapper', () => { const {container} = render( - - Content + + Content ); - expect(container.firstChild).toHaveClass(cardBoxStyles.blur); + const contentWrapper = container.querySelector(`.foregroundOpacity:not(.${cardBoxStyles.cardBg})`); + expect(contentWrapper).not.toBeNull(); + expect(contentWrapper).toHaveTextContent('Content'); }); + }); - it('sets backdrop blur CSS variable by default for translucent color', () => { + describe('outside box', () => { + it('wraps outsideBox children in foregroundOpacity', () => { const {container} = render( - - Content + + Content ); - expect(container.firstChild.style.getPropertyValue('--card-backdrop-blur')) - .toBe('blur(10px)'); + expect(container.querySelector('.foregroundOpacity')) + .toHaveTextContent('Content'); }); + }); - it('does not apply blur class when overlayBackdropBlur is 0', () => { + describe('overlay style', () => { + it('applies overlay style to background element', () => { const {container} = render( - + Content ); - expect(container.firstChild).not.toHaveClass(cardBoxStyles.blur); + const bg = container.querySelector(`.${cardBoxStyles.cardBg}`); + expect(bg).toHaveStyle({backgroundColor: '#ff000080'}); + expect(bg.style.backdropFilter).toBe('blur(5px)'); }); - it('sets backdrop blur CSS variable when color is translucent', () => { + it('does not set inline styles when overlayStyle is empty', () => { const {container} = render( - + Content ); - expect(container.firstChild.style.getPropertyValue('--card-backdrop-blur')) - .toBe('blur(5px)'); + const bg = container.querySelector(`.${cardBoxStyles.cardBg}`); + expect(bg.style.backdropFilter).toBeFalsy(); + expect(bg.style.backgroundColor).toBeFalsy(); }); }); describe('cardEnd padding', () => { it('does not have cardEndPadding class when lastMarginBottom is set', () => { const {container} = render( - + Content ); @@ -142,7 +142,8 @@ describe('CardBoxWrapper', () => { it('has cardEndPadding class when lastMarginBottom is not set', () => { const {container} = render( - + Content ); diff --git a/entry_types/scrolled/package/spec/frontend/foregroundBoxes/SplitBox-spec.js b/entry_types/scrolled/package/spec/frontend/foregroundBoxes/SplitBox-spec.js index 4c6b2232f5..99200d86d1 100644 --- a/entry_types/scrolled/package/spec/frontend/foregroundBoxes/SplitBox-spec.js +++ b/entry_types/scrolled/package/spec/frontend/foregroundBoxes/SplitBox-spec.js @@ -5,10 +5,13 @@ import '@testing-library/jest-dom/extend-expect'; import SplitBox from 'frontend/foregroundBoxes/SplitBox'; import styles from 'frontend/foregroundBoxes/SplitBox.module.css'; +const transitionStyles = {foregroundOpacity: 'foregroundOpacity'}; + describe('SplitBox', () => { it('renders children', () => { const {getByTestId} = render( - +
); @@ -18,7 +21,8 @@ describe('SplitBox', () => { it('applies paddingTop from motifAreaState', () => { const {container} = render( - +
); @@ -29,6 +33,7 @@ describe('SplitBox', () => { it('renders overlay when isContentPadded is true', () => { const {container} = render(
@@ -40,6 +45,7 @@ describe('SplitBox', () => { it('does not render overlay when isContentPadded is false', () => { const {container} = render(
@@ -51,6 +57,7 @@ describe('SplitBox', () => { it('sets overlay top from paddingTop', () => { const {container} = render(
@@ -63,6 +70,7 @@ describe('SplitBox', () => { it('applies dark overlay class when not inverted', () => { const {container} = render(
@@ -75,6 +83,7 @@ describe('SplitBox', () => { it('applies light overlay class when inverted', () => { const {container} = render(
@@ -84,44 +93,43 @@ describe('SplitBox', () => { .toHaveClass(styles.overlayLight); }); - it('applies backdrop filter when overlayBackdropBlur is set and color is translucent', () => { + it('applies foregroundOpacity class to overlay', () => { const {container} = render( + transitionStyles={transitionStyles} + inverted={false}>
); - expect(container.querySelector(`.${styles.overlay}`).style.backdropFilter) - .toBe('blur(5px)'); + expect(container.querySelector(`.${styles.overlay}`)) + .toHaveClass('foregroundOpacity'); }); - it('does not apply backdrop filter when color is opaque', () => { + it('applies foregroundOpacity class to content', () => { const {container} = render( - +
); - expect(container.querySelector(`.${styles.overlay}`).style.backdropFilter) - .toBeFalsy(); + expect(container.querySelector(`.${styles.content}`)) + .toHaveClass('foregroundOpacity'); }); - it('sets overlay background color from splitOverlayColor', () => { + it('applies overlay style to overlay element', () => { const {container} = render( + overlayStyle={{backgroundColor: '#ff000080', backdropFilter: 'blur(5px)'}}>
); - expect(container.querySelector(`.${styles.overlay}`)) - .toHaveStyle({backgroundColor: '#ff000080'}); + const overlay = container.querySelector(`.${styles.overlay}`); + expect(overlay).toHaveStyle({backgroundColor: '#ff000080'}); + expect(overlay.style.backdropFilter).toBe('blur(5px)'); }); }); diff --git a/entry_types/scrolled/package/spec/frontend/shadows/SplitShadow-spec.js b/entry_types/scrolled/package/spec/frontend/shadows/SplitShadow-spec.js index c6c3b6717b..8823a724ed 100644 --- a/entry_types/scrolled/package/spec/frontend/shadows/SplitShadow-spec.js +++ b/entry_types/scrolled/package/spec/frontend/shadows/SplitShadow-spec.js @@ -73,63 +73,29 @@ describe('SplitShadow', () => { expect(container.firstChild).toHaveClass(styles.light); }); - it('sets background color from splitOverlayColor prop', () => { - const {container} = render( - -
- - ); - - expect(container.querySelector(`.${styles.overlay}`)) - .toHaveStyle({backgroundColor: '#ff000080'}); - }); - - it('does not set inline background color when no splitOverlayColor', () => { - const {container} = render( - -
- - ); - - expect(container.querySelector(`.${styles.overlay}`).style.backgroundColor) - .toBe(''); - }); - - it('applies backdrop filter when overlayBackdropBlur is set and color is translucent', () => { - const {container} = render( - -
- - ); - - expect(container.querySelector(`.${styles.overlay}`).style.backdropFilter) - .toBe('blur(5px)'); - }); - - it('does not apply backdrop filter when color is opaque', () => { + it('applies overlay style to overlay element', () => { const {container} = render( + overlayStyle={{backgroundColor: '#ff000080', backdropFilter: 'blur(5px)'}}>
); - expect(container.querySelector(`.${styles.overlay}`).style.backdropFilter) - .toBeFalsy(); + const overlay = container.querySelector(`.${styles.overlay}`); + expect(overlay).toHaveStyle({backgroundColor: '#ff000080'}); + expect(overlay.style.backdropFilter).toBe('blur(5px)'); }); - it('applies default backdrop filter when no color is set', () => { + it('does not set inline styles when no overlayStyle is passed', () => { const {container} = render(
); - expect(container.querySelector(`.${styles.overlay}`).style.backdropFilter) - .toBe('blur(10px)'); + const overlay = container.querySelector(`.${styles.overlay}`); + expect(overlay.style.backgroundColor).toBe(''); + expect(overlay.style.backdropFilter).toBeFalsy(); }); it('does not render overlay when isContentPadded is true', () => { diff --git a/entry_types/scrolled/package/spec/frontend/splitOverlayStyle-spec.js b/entry_types/scrolled/package/spec/frontend/splitOverlayStyle-spec.js deleted file mode 100644 index a2489e9a07..0000000000 --- a/entry_types/scrolled/package/spec/frontend/splitOverlayStyle-spec.js +++ /dev/null @@ -1,43 +0,0 @@ -import {splitOverlayStyle} from 'frontend/splitOverlayStyle'; - -describe('splitOverlayStyle', () => { - it('sets backgroundColor from color', () => { - expect(splitOverlayStyle({color: '#ff0000'})) - .toEqual({backgroundColor: '#ff0000'}); - }); - - it('sets backdropFilter when color is translucent and backdropBlur is set', () => { - expect(splitOverlayStyle({color: '#ff000080', backdropBlur: 50})) - .toEqual({backgroundColor: '#ff000080', backdropFilter: 'blur(5px)'}); - }); - - it('does not set backdropFilter when color is opaque', () => { - expect(splitOverlayStyle({color: '#ff0000', backdropBlur: 50})) - .toEqual({backgroundColor: '#ff0000'}); - }); - - it('does not set backdropFilter when backdropBlur is 0', () => { - expect(splitOverlayStyle({color: '#ff000080', backdropBlur: 0})) - .toEqual({backgroundColor: '#ff000080'}); - }); - - it('scales blur value to max 10px', () => { - expect(splitOverlayStyle({color: '#ff000080', backdropBlur: 100})) - .toEqual({backgroundColor: '#ff000080', backdropFilter: 'blur(10px)'}); - }); - - it('defaults backdropFilter for translucent color', () => { - expect(splitOverlayStyle({color: '#ff000080'})) - .toEqual({backgroundColor: '#ff000080', backdropFilter: 'blur(10px)'}); - }); - - it('defaults backdropFilter when no color is set', () => { - expect(splitOverlayStyle({})) - .toEqual({backdropFilter: 'blur(10px)'}); - }); - - it('does not default backdropFilter for opaque color', () => { - expect(splitOverlayStyle({color: '#ff0000'})) - .toEqual({backgroundColor: '#ff0000'}); - }); -}); diff --git a/entry_types/scrolled/package/spec/frontend/transitions-spec.js b/entry_types/scrolled/package/spec/frontend/transitions-spec.js index 057743d1ac..63f5d1154e 100644 --- a/entry_types/scrolled/package/spec/frontend/transitions-spec.js +++ b/entry_types/scrolled/package/spec/frontend/transitions-spec.js @@ -1,9 +1,12 @@ import { getTransitionNames, getAvailableTransitionNames, + getTransitionStyles, getTransitionStylesName } from 'frontend/transitions'; +import sharedTransitionStyles from 'frontend/transitions/shared.module.css'; + import {useEntryStructure} from 'entryState/structure'; import {renderHookInEntry} from 'support'; @@ -51,6 +54,61 @@ describe('getAvailableTransitions', () => { }); }); +describe('getTransitionStyles', () => { + it('includes perElementFade class in foreground when overlayStyle has backdropFilter', () => { + const {result} = renderHookInEntry(() => useEntryStructure(), { + seed: { + sections: [ + {configuration: {fullHeight: true, transition: 'scroll'}}, + {configuration: {fullHeight: true, transition: 'fade'}}, + {configuration: {fullHeight: true, transition: 'scroll'}} + ] + } + }); + + const styles = getTransitionStyles( + result.current.main[0].sections[1], + {backdropFilter: 'blur(10px)'} + ); + + expect(styles.foreground).toContain(sharedTransitionStyles.perElementFade); + }); + + it('does not include perElementFade class in foreground by default', () => { + const {result} = renderHookInEntry(() => useEntryStructure(), { + seed: { + sections: [ + {configuration: {fullHeight: true, transition: 'scroll'}}, + {configuration: {fullHeight: true, transition: 'fade'}}, + {configuration: {fullHeight: true, transition: 'scroll'}} + ] + } + }); + + const styles = getTransitionStyles(result.current.main[0].sections[1]); + + expect(styles.foreground).not.toContain(sharedTransitionStyles.perElementFade); + }); + + it('does not include perElementFade class for scroll-only transitions', () => { + const {result} = renderHookInEntry(() => useEntryStructure(), { + seed: { + sections: [ + {configuration: {transition: 'scroll'}} + ] + } + }); + + const styles = getTransitionStyles( + result.current.main[0].sections[0], + {backdropFilter: 'blur(10px)'} + ); + + expect(styles.foreground).toBeUndefined(); + }); + +}); + describe('getTransitionStylesName', () => { it('uses fadeIn if both section and previous section have fullHeight', () => { const {result} = renderHookInEntry(() => useEntryStructure(), { diff --git a/entry_types/scrolled/package/spec/support/pageObjects.js b/entry_types/scrolled/package/spec/support/pageObjects.js index 575b740e97..9cc51aa54d 100644 --- a/entry_types/scrolled/package/spec/support/pageObjects.js +++ b/entry_types/scrolled/package/spec/support/pageObjects.js @@ -225,6 +225,10 @@ function createSectionPageObject(el) { return foreground.classList.contains(sharedTransitionStyles.fadedOut); }, + usesPerElementFadeTransition() { + return foreground.classList.contains(sharedTransitionStyles.perElementFade); + }, + getPaddingIndicator(position) { const {getByLabelText} = within(selectionRect); const labels = { diff --git a/entry_types/scrolled/package/src/frontend/Section.js b/entry_types/scrolled/package/src/frontend/Section.js index 6a2caaaa16..a2211d5ea5 100644 --- a/entry_types/scrolled/package/src/frontend/Section.js +++ b/entry_types/scrolled/package/src/frontend/Section.js @@ -22,7 +22,7 @@ import {BackgroundColorProvider} from './backgroundColor'; import {SelectableWidget} from './SelectableWidget'; import {useSectionPadding} from './useSectionPaddingCustomProperties'; import {SectionIntersectionProbe} from './SectionIntersectionObserver'; -import {getAppearanceComponents, getAppearanceSectionScopeName} from './appearance'; +import {getAppearanceComponents, getAppearanceSectionScopeName, useAppearanceOverlayStyle} from './appearance'; import * as v1 from './v1'; import * as v2 from './v2'; @@ -40,7 +40,8 @@ const Section = withInlineEditingDecorator('SectionDecorator', function Section( const ref = useScrollTarget(section.id); - const transitionStyles = getTransitionStyles(section); + const sectionOverlayStyle = useAppearanceOverlayStyle(section); + const transitionStyles = getTransitionStyles(section, sectionOverlayStyle); const backdropSectionClassNames = useBackdropSectionClassNames(backdrop, { layout: section.layout, @@ -83,6 +84,7 @@ const Section = withInlineEditingDecorator('SectionDecorator', function Section( contentElements={contentElements} state={state} transitionStyles={transitionStyles} + sectionOverlayStyle={sectionOverlayStyle} sectionPadding={sectionPadding} /> + overlayStyle={sectionOverlayStyle}> {children} } @@ -176,8 +177,7 @@ function SectionContents({ state={state} motifAreaState={motifAreaState} staticShadowOpacity={staticShadowOpacity} - splitOverlayColor={section.splitOverlayColor} - overlayBackdropBlur={section.overlayBackdropBlur}> + overlayStyle={sectionOverlayStyle}> {(children, boxProps) => {children} } diff --git a/entry_types/scrolled/package/src/frontend/appearance.js b/entry_types/scrolled/package/src/frontend/appearance/index.js similarity index 60% rename from entry_types/scrolled/package/src/frontend/appearance.js rename to entry_types/scrolled/package/src/frontend/appearance/index.js index 7a75d4625f..483f49fb9e 100644 --- a/entry_types/scrolled/package/src/frontend/appearance.js +++ b/entry_types/scrolled/package/src/frontend/appearance/index.js @@ -1,12 +1,14 @@ -import NoOpShadow from './shadows/NoOpShadow'; -import GradientShadow from './shadows/GradientShadow'; -import SplitShadow from './shadows/SplitShadow'; +import NoOpShadow from '../shadows/NoOpShadow'; +import GradientShadow from '../shadows/GradientShadow'; +import SplitShadow from '../shadows/SplitShadow'; -import {InvisibleBoxWrapper} from './foregroundBoxes/InvisibleBoxWrapper'; -import GradientBox from './foregroundBoxes/GradientBox'; -import CardBox from "./foregroundBoxes/CardBox"; -import CardBoxWrapper from "./foregroundBoxes/CardBoxWrapper"; -import SplitBox from "./foregroundBoxes/SplitBox"; +import {InvisibleBoxWrapper} from '../foregroundBoxes/InvisibleBoxWrapper'; +import GradientBox from '../foregroundBoxes/GradientBox'; +import CardBox from "../foregroundBoxes/CardBox"; +import CardBoxWrapper from "../foregroundBoxes/CardBoxWrapper"; +import SplitBox from "../foregroundBoxes/SplitBox"; + +export {useAppearanceOverlayStyle} from './useAppearanceOverlayStyle'; const components = { shadow: { diff --git a/entry_types/scrolled/package/src/frontend/appearance/useAppearanceOverlayStyle.js b/entry_types/scrolled/package/src/frontend/appearance/useAppearanceOverlayStyle.js new file mode 100644 index 0000000000..73912d3c85 --- /dev/null +++ b/entry_types/scrolled/package/src/frontend/appearance/useAppearanceOverlayStyle.js @@ -0,0 +1,47 @@ +import {useMemo} from 'react'; + +import {isTranslucentColor} from '../utils/isTranslucentColor'; + +export function useAppearanceOverlayStyle(section) { + const {appearance, cardSurfaceColor, splitOverlayColor, overlayBackdropBlur} = section; + + return useMemo(() => { + if (appearance === 'cards') { + return overlayStyle(cardSurfaceColor, overlayBackdropBlur); + } + else if (appearance === 'split') { + return overlayStyle(splitOverlayColor, overlayBackdropBlur, true); + } + + return {}; + }, [appearance, cardSurfaceColor, splitOverlayColor, overlayBackdropBlur]); +} + +function overlayStyle(color, backdropBlur, blurByDefault) { + const style = {}; + + if (color) { + style.backgroundColor = color; + } + + const blur = resolvedBackdropBlur(color, backdropBlur, blurByDefault); + + if (blur > 0) { + style.backdropFilter = `blur(${blur / 100 * 10}px)`; + } + + return style; +} + +function resolvedBackdropBlur(color, backdropBlur, blurByDefault) { + if (blurByDefault) { + if (color && !isTranslucentColor(color)) { + return 0; + } + } + else if (!isTranslucentColor(color)) { + return 0; + } + + return backdropBlur ?? 100; +} diff --git a/entry_types/scrolled/package/src/frontend/foregroundBoxes/CardBoxWrapper.js b/entry_types/scrolled/package/src/frontend/foregroundBoxes/CardBoxWrapper.js index ace4a857cb..cf34c0ef28 100644 --- a/entry_types/scrolled/package/src/frontend/foregroundBoxes/CardBoxWrapper.js +++ b/entry_types/scrolled/package/src/frontend/foregroundBoxes/CardBoxWrapper.js @@ -4,7 +4,6 @@ import classNames from 'classnames'; import {widths} from '../layouts'; import {BackgroundColorProvider} from '../backgroundColor'; import {TrimDefaultMarginTop} from '../TrimDefaultMarginTop'; -import {isTranslucentColor} from '../utils/isTranslucentColor'; import styles from "./CardBoxWrapper.module.css"; import boundaryMarginStyles from "./BoxBoundaryMargin.module.css"; @@ -13,17 +12,22 @@ export default function CardBoxWrapper(props) { if (outsideBox(props)) { return ( - {props.children} +
+ {props.children} +
); } return ( -
- - {props.children} - +
+
+
+ + {props.children} + +
) } @@ -34,30 +38,10 @@ function outsideBox(props) { props.customMargin } -function cardStyle(props) { - const style = {'--card-surface-color': props.cardSurfaceColor}; - const blur = resolvedBackdropBlur(props); - - if (blur > 0) { - style['--card-backdrop-blur'] = `blur(${blur / 100 * 10}px)`; - } - - return style; -} - -function resolvedBackdropBlur(props) { - if (!isTranslucentColor(props.cardSurfaceColor)) { - return 0; - } - - return props.overlayBackdropBlur ?? 100; -} - function className(props) { return classNames( styles.card, props.inverted ? styles.cardBgBlack : styles.cardBgWhite, - {[styles.blur]: resolvedBackdropBlur(props) > 0}, {[styles.cardStart]: !props.openStart}, {[styles.cardEnd]: !props.openEnd}, {[styles.cardEndPadding]: !props.openEnd && !props.lastMarginBottom}, diff --git a/entry_types/scrolled/package/src/frontend/foregroundBoxes/CardBoxWrapper.module.css b/entry_types/scrolled/package/src/frontend/foregroundBoxes/CardBoxWrapper.module.css index bb9060a4dd..44d6add0a2 100644 --- a/entry_types/scrolled/package/src/frontend/foregroundBoxes/CardBoxWrapper.module.css +++ b/entry_types/scrolled/package/src/frontend/foregroundBoxes/CardBoxWrapper.module.css @@ -13,8 +13,11 @@ padding: 0 1.5em; } -.card::before { - content: ''; +.cardContent { + display: flow-root; +} + +.cardBg { position: absolute; top: 0; left: 0; @@ -35,13 +38,12 @@ padding-bottom: 1.5em; } -.cardStart::before { +.cardStart > .cardBg { border-top-left-radius: var(--theme-cards-border-radius, 15px); border-top-right-radius: var(--theme-cards-border-radius, 15px); } - -.cardEnd::before { +.cardEnd > .cardBg { border-bottom-left-radius: var(--theme-cards-border-radius, 15px); border-bottom-right-radius: var(--theme-cards-border-radius, 15px); } @@ -54,17 +56,13 @@ composes: scope-lightContent from global; } -.blur::before { - backdrop-filter: var(--card-backdrop-blur); -} - @media screen { - .cardBgWhite::before { - background-color: var(--card-surface-color, lightContentSurfaceColor); + .cardBgWhite > .cardBg { + background-color: lightContentSurfaceColor; } - .cardBgBlack::before { - background-color: var(--card-surface-color, darkContentSurfaceColor); + .cardBgBlack > .cardBg { + background-color: darkContentSurfaceColor; } .cardBgWhite { diff --git a/entry_types/scrolled/package/src/frontend/foregroundBoxes/SplitBox.js b/entry_types/scrolled/package/src/frontend/foregroundBoxes/SplitBox.js index 9d28c6b9bf..61963f1159 100644 --- a/entry_types/scrolled/package/src/frontend/foregroundBoxes/SplitBox.js +++ b/entry_types/scrolled/package/src/frontend/foregroundBoxes/SplitBox.js @@ -1,7 +1,6 @@ import React from 'react'; import classNames from 'classnames'; -import {splitOverlayStyle} from '../splitOverlayStyle'; import styles from './SplitBox.module.css'; export default function SplitBox(props) { @@ -9,15 +8,15 @@ export default function SplitBox(props) {
{props.motifAreaState.isContentPadded &&
} -
+
{props.children}
diff --git a/entry_types/scrolled/package/src/frontend/shadows/SplitShadow.js b/entry_types/scrolled/package/src/frontend/shadows/SplitShadow.js index 584e2f5d86..a593b85c38 100644 --- a/entry_types/scrolled/package/src/frontend/shadows/SplitShadow.js +++ b/entry_types/scrolled/package/src/frontend/shadows/SplitShadow.js @@ -2,7 +2,6 @@ import React from 'react'; import classNames from 'classnames'; import Fullscreen from '../Fullscreen'; -import {splitOverlayStyle} from '../splitOverlayStyle'; import styles from './SplitShadow.module.css'; export default function SplitShadow(props) { @@ -19,10 +18,7 @@ export default function SplitShadow(props) { styles[`align-${props.align}`], props.inverted ? styles.light : styles.dark)}>
+ style={props.overlayStyle}>
{props.children} diff --git a/entry_types/scrolled/package/src/frontend/splitOverlayStyle.js b/entry_types/scrolled/package/src/frontend/splitOverlayStyle.js deleted file mode 100644 index fbe318b6f2..0000000000 --- a/entry_types/scrolled/package/src/frontend/splitOverlayStyle.js +++ /dev/null @@ -1,25 +0,0 @@ -import {isTranslucentColor} from './utils/isTranslucentColor'; - -export function splitOverlayStyle({color, backdropBlur}) { - const style = {}; - - if (color) { - style.backgroundColor = color; - } - - const blur = resolvedBackdropBlur({color, backdropBlur}); - - if (blur > 0) { - style.backdropFilter = `blur(${blur / 100 * 10}px)`; - } - - return style; -} - -function resolvedBackdropBlur({color, backdropBlur}) { - if (color && !isTranslucentColor(color)) { - return 0; - } - - return backdropBlur ?? 100; -} diff --git a/entry_types/scrolled/package/src/frontend/transitions/index.js b/entry_types/scrolled/package/src/frontend/transitions/index.js index 7facd5840a..be05da0393 100644 --- a/entry_types/scrolled/package/src/frontend/transitions/index.js +++ b/entry_types/scrolled/package/src/frontend/transitions/index.js @@ -18,6 +18,8 @@ import scrollInFadeOut from './scrollInFadeOut.module.css'; import scrollInFadeOutBg from './scrollInFadeOutBg.module.css'; import scrollInScrollOut from './scrollInScrollOut.module.css'; +import sharedStyles from './shared.module.css'; + const styles = { fadeInBgConceal, fadeInBgFadeOut, @@ -70,14 +72,29 @@ export function getAvailableTransitionNames(section, previousSection) { return getTransitionNames(); } -export function getTransitionStyles(section) { +export function getTransitionStyles(section, overlayStyle) { const name = getTransitionStylesName(section); if (!styles[name]) { throw new Error(`Unknown transition ${name}`); } - return styles[name]; + const base = styles[name]; + + const [enter, exit] = getEnterAndExitTransitions(section); + + if (overlayStyle?.backdropFilter && + (enter.startsWith('fade') || exit.startsWith('fade'))) { + return { + ...base, + foreground: base.foreground + ? `${base.foreground} ${sharedStyles.perElementFade}` + : sharedStyles.perElementFade, + foregroundOpacity: sharedStyles.foregroundOpacity + }; + } + + return base; } export function getEnterAndExitTransitions(section) { diff --git a/entry_types/scrolled/package/src/frontend/transitions/shared.module.css b/entry_types/scrolled/package/src/frontend/transitions/shared.module.css index e2acf9c077..8551ba95f7 100644 --- a/entry_types/scrolled/package/src/frontend/transitions/shared.module.css +++ b/entry_types/scrolled/package/src/frontend/transitions/shared.module.css @@ -1,3 +1,5 @@ +@value fade-duration from './values.module.css'; + .fixed { position: fixed; top: calc(var(--fixed-positioning-containing-block-offset, 0px) + @@ -20,3 +22,21 @@ opacity: 0; visibility: hidden; } + +/* + Override transition set on .foreground in individual transition + modules (e.g. scrollInFadeOut) to animate --foreground-opacity + instead of opacity. +*/ +.perElementFade { + transition: --foreground-opacity fade-duration ease, visibility fade-duration !important; +} + +.perElementFade.fadedOut { + --foreground-opacity: 0; + opacity: 1; +} + +.perElementFade .foregroundOpacity { + opacity: var(--foreground-opacity, 1); +} diff --git a/entry_types/scrolled/package/values/properties.css b/entry_types/scrolled/package/values/properties.css index abe516401e..5718829bf7 100644 --- a/entry_types/scrolled/package/values/properties.css +++ b/entry_types/scrolled/package/values/properties.css @@ -3,3 +3,9 @@ inherits: true; initial-value: 9999px; } + +@property --foreground-opacity { + syntax: ""; + inherits: true; + initial-value: 1; +}