Skip to content

Commit

Permalink
chore: Fix funnel support for modals
Browse files Browse the repository at this point in the history
  • Loading branch information
connorlanigan committed Sep 12, 2023
1 parent 4def8f6 commit 06fe848
Show file tree
Hide file tree
Showing 8 changed files with 74 additions and 23 deletions.
12 changes: 10 additions & 2 deletions src/container/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { getExternalProps } from '../internal/utils/external-props';
import { applyDisplayName } from '../internal/utils/apply-display-name';
import useBaseComponent from '../internal/hooks/use-base-component';
import { AnalyticsFunnelSubStep } from '../internal/analytics/components/analytics-funnel';
import { useFunnelSubStep } from '../internal/analytics/hooks/use-funnel';

export { ContainerProps };

Expand All @@ -18,17 +19,24 @@ export default function Container({
}: ContainerProps) {
const baseComponentProps = useBaseComponent('Container');
const externalProps = getExternalProps(props);

return (
<AnalyticsFunnelSubStep>
<InternalContainer
<InternalContainerAsSubstep
variant={variant}
disableHeaderPaddings={disableHeaderPaddings}
disableContentPaddings={disableContentPaddings}
disableHeaderPaddings={disableHeaderPaddings}
{...props}
{...externalProps}
{...baseComponentProps}
/>
</AnalyticsFunnelSubStep>
);
}

function InternalContainerAsSubstep(props: ContainerProps) {
const { subStepRef, funnelSubStepProps } = useFunnelSubStep();
return <InternalContainer {...props} __subStepRef={subStepRef} __funnelSubStepProps={funnelSubStepProps} />;
}

applyDisplayName(Container, 'Container');
12 changes: 8 additions & 4 deletions src/container/internal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import { useMergeRefs } from '../internal/hooks/use-merge-refs';
import { useMobile } from '../internal/hooks/use-mobile';
import { useVisualRefresh } from '../internal/hooks/use-visual-mode';
import styles from './styles.css.js';
import { useFunnelSubStep } from '../internal/analytics/hooks/use-funnel';
import type { useFunnelSubStep } from '../internal/analytics/hooks/use-funnel';

export interface InternalContainerProps extends Omit<ContainerProps, 'variant'>, InternalBaseComponentProps {
__stickyHeader?: boolean;
Expand All @@ -31,6 +31,9 @@ export interface InternalContainerProps extends Omit<ContainerProps, 'variant'>,
* * `full-page` – Only for internal use in table, cards and other components
*/
variant?: ContainerProps['variant'] | 'embedded' | 'full-page' | 'cards';

__funnelSubStepProps?: ReturnType<typeof useFunnelSubStep>['funnelSubStepProps'];
__subStepRef?: ReturnType<typeof useFunnelSubStep>['subStepRef'];
}

export default function InternalContainer({
Expand All @@ -52,6 +55,8 @@ export default function InternalContainer({
__headerRef,
__darkHeader = false,
__disableStickyMobile = true,
__funnelSubStepProps,
__subStepRef,
...restProps
}: InternalContainerProps) {
const isMobile = useMobile();
Expand All @@ -68,12 +73,11 @@ export default function InternalContainer({
);
const { setHasStickyBackground } = useAppLayoutContext();
const isRefresh = useVisualRefresh();
const { subStepRef, funnelSubStepProps } = useFunnelSubStep();

const hasDynamicHeight = isRefresh && variant === 'full-page';
const overlapElement = useDynamicOverlap({ disabled: !hasDynamicHeight || !__darkHeader });

const mergedRef = useMergeRefs(rootRef, subStepRef, __internalRootRef);
const mergedRef = useMergeRefs(rootRef, __subStepRef, __internalRootRef);
const headerMergedRef = useMergeRefs(headerRef, overlapElement, __headerRef);

/**
Expand Down Expand Up @@ -104,7 +108,7 @@ export default function InternalContainer({
return (
<div
{...baseProps}
{...funnelSubStepProps}
{...__funnelSubStepProps}
className={clsx(
baseProps.className,
styles.root,
Expand Down
10 changes: 8 additions & 2 deletions src/expandable-section/expandable-section-container.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,19 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
import InternalContainer from '../container/internal';
import InternalContainer, { InternalContainerProps } from '../container/internal';
import React from 'react';
import { ExpandableSectionProps } from './interfaces';
import { InternalBaseComponentProps } from '../internal/hooks/use-base-component';

interface ExpandableSectionContainerProps extends InternalBaseComponentProps {
export interface ExpandableSectionContainerProps extends InternalBaseComponentProps {
className?: string;
header: React.ReactNode;
children?: React.ReactNode;
variant: ExpandableSectionProps.Variant;
expanded: boolean | undefined;
disableContentPaddings: boolean | undefined;
__funnelSubStepProps?: InternalContainerProps['__funnelSubStepProps'];
__subStepRef?: InternalContainerProps['__subStepRef'];
}

export const ExpandableSectionContainer = ({
Expand All @@ -22,6 +24,8 @@ export const ExpandableSectionContainer = ({
expanded,
disableContentPaddings,
__internalRootRef,
__funnelSubStepProps,
__subStepRef,
...rest
}: ExpandableSectionContainerProps) => {
if (variant === 'container' || variant === 'stacked') {
Expand All @@ -35,6 +39,8 @@ export const ExpandableSectionContainer = ({
disableHeaderPaddings={true}
__hiddenContent={!expanded}
__internalRootRef={__internalRootRef}
__funnelSubStepProps={__funnelSubStepProps}
__subStepRef={__subStepRef}
>
{children}
</InternalContainer>
Expand Down
19 changes: 14 additions & 5 deletions src/expandable-section/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,22 +4,31 @@ import React from 'react';

import { ExpandableSectionProps } from './interfaces';
import { applyDisplayName } from '../internal/utils/apply-display-name';
import InternalExpandableSection from './internal';
import InternalExpandableSection, { InternalExpandableSectionProps } from './internal';
import useBaseComponent from '../internal/hooks/use-base-component';
import { AnalyticsFunnelSubStep } from '../internal/analytics/components/analytics-funnel';
import { useFunnelSubStep } from '../internal/analytics/hooks/use-funnel';

export { ExpandableSectionProps };

export default function ExpandableSection({ variant = 'default', ...props }: ExpandableSectionProps) {
const baseComponentProps = useBaseComponent('ExpandableSection');

const expandableSection = <InternalExpandableSection variant={variant} {...props} {...baseComponentProps} />;

if (variant === 'container' || variant === 'stacked') {
return <AnalyticsFunnelSubStep>{expandableSection}</AnalyticsFunnelSubStep>;
return (
<AnalyticsFunnelSubStep>
<InternalExpandableSectionAsSubstep variant={variant} {...props} {...baseComponentProps} />
</AnalyticsFunnelSubStep>
);
} else {
return expandableSection;
return <InternalExpandableSection variant={variant} {...props} {...baseComponentProps} />;
}
}

function InternalExpandableSectionAsSubstep(props: InternalExpandableSectionProps) {
const { subStepRef, funnelSubStepProps } = useFunnelSubStep();

return <InternalExpandableSection {...props} __subStepRef={subStepRef} __funnelSubStepProps={funnelSubStepProps} />;
}

applyDisplayName(ExpandableSection, 'ExpandableSection');
10 changes: 8 additions & 2 deletions src/expandable-section/internal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,14 @@ import { fireNonCancelableEvent } from '../internal/events';
import { ExpandableSectionProps } from './interfaces';

import styles from './styles.css.js';
import { ExpandableSectionContainer } from './expandable-section-container';
import { ExpandableSectionContainer, ExpandableSectionContainerProps } from './expandable-section-container';
import { ExpandableSectionHeader } from './expandable-section-header';
import { InternalBaseComponentProps } from '../internal/hooks/use-base-component';
import { variantSupportsDescription } from './utils';

type InternalExpandableSectionProps = ExpandableSectionProps & InternalBaseComponentProps;
export type InternalExpandableSectionProps = ExpandableSectionProps &
InternalBaseComponentProps &
Pick<ExpandableSectionContainerProps, '__funnelSubStepProps' | '__subStepRef'>;

export default function InternalExpandableSection({
expanded: controlledExpanded,
Expand All @@ -36,6 +38,8 @@ export default function InternalExpandableSection({
disableContentPaddings,
headerAriaLabel,
__internalRootRef,
__funnelSubStepProps,
__subStepRef,
...props
}: InternalExpandableSectionProps) {
const ref = useRef<HTMLDivElement>(null);
Expand Down Expand Up @@ -98,6 +102,8 @@ export default function InternalExpandableSection({
expanded={expanded}
className={clsx(baseProps.className, styles.root)}
variant={variant}
__funnelSubStepProps={__funnelSubStepProps}
__subStepRef={__subStepRef}
disableContentPaddings={disableContentPaddings}
header={
<ExpandableSectionHeader
Expand Down
17 changes: 14 additions & 3 deletions src/internal/analytics/components/analytics-funnel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import {
getSubStepSelector,
} from '../selectors';
import { useDebounceCallback } from '../../hooks/use-debounce-callback';
import { nodeBelongs } from '../../utils/node-belongs';

export const FUNNEL_VERSION = '1.1';

Expand Down Expand Up @@ -349,7 +350,7 @@ const InnerAnalyticsFunnelStep = ({ children, stepNumber, stepNameSelector }: An
);
};
interface AnalyticsFunnelSubStepProps {
children?: React.ReactNode;
children?: React.ReactNode | ((props: FunnelSubStepContextValue) => React.ReactNode);
}

export const AnalyticsFunnelSubStep = ({ children }: AnalyticsFunnelSubStepProps) => {
Expand Down Expand Up @@ -395,6 +396,10 @@ export const AnalyticsFunnelSubStep = ({ children }: AnalyticsFunnelSubStepProps
const context = isNested ? inheritedContext : newContext;

useEffect(() => {
if (isNested || !subStepRef.current) {
return;
}

const onMouseDown = () => (mousePressed.current = true);

const onMouseUp = async () => {
Expand All @@ -412,7 +417,7 @@ export const AnalyticsFunnelSubStep = ({ children }: AnalyticsFunnelSubStepProps
*/
await new Promise(r => setTimeout(r, 1));

if (!subStepRef.current || !subStepRef.current.contains(document.activeElement)) {
if (!subStepRef.current || !document.activeElement || !nodeBelongs(subStepRef.current, document.activeElement)) {
isFocusedSubStep.current = false;

/*
Expand All @@ -436,7 +441,13 @@ export const AnalyticsFunnelSubStep = ({ children }: AnalyticsFunnelSubStepProps
subStepNameSelector,
subStepSelector,
focusCleanupFunction,
isNested,
subStepRef,
]);

return <FunnelSubStepContext.Provider value={context}>{children}</FunnelSubStepContext.Provider>;
return (
<FunnelSubStepContext.Provider value={context}>
{typeof children === 'function' ? children(context) : children}
</FunnelSubStepContext.Provider>
);
};
3 changes: 2 additions & 1 deletion src/internal/analytics/hooks/use-funnel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
getSubStepAllSelector,
} from '../selectors';
import { FunnelMetrics } from '../';
import { nodeBelongs } from '../../utils/node-belongs';

/**
* Custom React Hook to manage and interact with FunnelSubStep.
Expand Down Expand Up @@ -121,7 +122,7 @@ export const useFunnelSubStep = () => {
return;
}

if (!subStepRef.current || !subStepRef.current.contains(event.relatedTarget) || !event.relatedTarget) {
if (!subStepRef.current || !event.relatedTarget || !nodeBelongs(subStepRef.current, event.relatedTarget)) {
isFocusedSubStep.current = false;

if (funnelInteractionId && subStepId && funnelState.current !== 'cancelled') {
Expand Down
14 changes: 10 additions & 4 deletions src/modal/internal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,14 @@ import { ButtonContext } from '../internal/context/button-context';
type InternalModalProps = SomeRequired<ModalProps, 'size'> & InternalBaseComponentProps;

export default function InternalModal({ modalRoot, ...rest }: InternalModalProps) {
const referrerId = useUniqueId('modal');

return (
<Portal container={modalRoot}>
<InnerModal {...rest} />
</Portal>
<div id={referrerId}>
<Portal container={modalRoot}>
<InnerModal {...rest} referrerId={referrerId} />
</Portal>
</div>
);
}

Expand All @@ -47,8 +51,9 @@ function InnerModal({
disableContentPaddings,
onDismiss,
__internalRootRef = null,
referrerId,
...rest
}: InternalModalProps) {
}: InternalModalProps & { referrerId: string }) {
const instanceUniqueId = useUniqueId();
const headerId = `${rest.id || instanceUniqueId}-header`;
const lastMouseDownElementRef = useRef<HTMLElement | null>(null);
Expand Down Expand Up @@ -128,6 +133,7 @@ function InnerModal({
onClick={onOverlayClick}
ref={mergedRef}
style={footerHeight ? { scrollPaddingBottom: footerHeight } : undefined}
data-awsui-referrer-id={referrerId}
>
<FocusLock disabled={!visible} autoFocus={true} restoreFocus={true} className={styles['focus-lock']}>
<div
Expand Down

0 comments on commit 06fe848

Please sign in to comment.