Skip to content

Commit

Permalink
chore: Add initialCheck capability to alert/flash content API
Browse files Browse the repository at this point in the history
  • Loading branch information
gethinwebster committed Oct 16, 2024
1 parent 7ff5589 commit 31cf7ec
Show file tree
Hide file tree
Showing 5 changed files with 67 additions and 12 deletions.
11 changes: 9 additions & 2 deletions pages/alert/runtime-content.page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,13 @@ awsuiPlugins.alertContent.registerContentReplacer({
},
};
},
initialCheck(context) {
const found = context.type === 'error' && context.contentText?.match('Access denied');
return {
header: found ? 'remove' : 'original',
content: found ? 'replaced' : 'original',
};
},
});

const alertTypeOptions = ['error', 'warning', 'info', 'success'].map(type => ({ value: type }));
Expand Down Expand Up @@ -106,15 +113,15 @@ export default function () {
<ScreenshotArea gutters={false}>
{hidden ? null : (
<SpaceBetween size="m">
<Alert
{/* <Alert
type={type}
statusIconAriaLabel={type}
dismissAriaLabel="Dismiss"
header="Header"
action={<Button>Action</Button>}
>
{!contentSwapped ? content1 : content2}
</Alert>
</Alert> */}

<Alert
type={type}
Expand Down
2 changes: 1 addition & 1 deletion src/alert/internal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ const typeToIcon: Record<AlertProps.Type, IconProps['name']> = {
type InternalAlertProps = SomeRequired<AlertProps, 'type'> & InternalBaseComponentProps<HTMLDivElement>;

const useDiscoveredAction = createUseDiscoveredAction(awsuiPluginsInternal.alert.onActionRegistered);
const useDiscoveredContent = createUseDiscoveredContent('alert', awsuiPluginsInternal.alertContent.onContentRegistered);
const useDiscoveredContent = createUseDiscoveredContent('alert', awsuiPluginsInternal.alertContent);

const InternalAlert = React.forwardRef(
(
Expand Down
2 changes: 1 addition & 1 deletion src/flashbar/flash.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ const ICON_TYPES = {
} as const;

const useDiscoveredAction = createUseDiscoveredAction(awsuiPluginsInternal.flashbar.onActionRegistered);
const useDiscoveredContent = createUseDiscoveredContent('flash', awsuiPluginsInternal.flashContent.onContentRegistered);
const useDiscoveredContent = createUseDiscoveredContent('flash', awsuiPluginsInternal.flashContent);

function dismissButton(
dismissLabel: FlashbarProps.MessageDefinition['dismissLabel'],
Expand Down
44 changes: 44 additions & 0 deletions src/internal/plugins/controllers/alert-flash-content.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

import { ReactNode } from 'react';
import flattenChildren from 'react-keyed-flatten-children';

import debounce from '../../debounce';

// this code should not depend on React typings, because it is portable between major versions
Expand All @@ -14,6 +17,18 @@ export interface AlertFlashContentContext {
contentRef: RefShim<HTMLElement>;
}

interface AlertFlashContentInitialContextRaw {
type: string;
header?: ReactNode;
content?: ReactNode;
}

export interface AlertFlashContentInitialContext {
type: string;
headerText?: string;
contentText?: string;
}

export type ReplacementType = 'original' | 'remove' | 'replaced';

export interface ReplacementApi {
Expand All @@ -30,9 +45,15 @@ export interface AlertFlashContentResult {
unmount: (containers: { replacementHeaderContainer: HTMLElement; replacementContentContainer: HTMLElement }) => void;
}

export interface AlertFlashContentInitialResult {
header: ReplacementType;
content: ReplacementType;
}

export interface AlertFlashContentConfig {
id: string;
runReplacer: (context: AlertFlashContentContext, replacementApi: ReplacementApi) => AlertFlashContentResult;
initialCheck?: (context: AlertFlashContentInitialContext) => AlertFlashContentInitialResult;
}

export type AlertFlashContentRegistrationListener = (provider: AlertFlashContentConfig) => () => void;
Expand All @@ -44,8 +65,15 @@ export interface AlertFlashContentApiPublic {
export interface AlertFlashContentApiInternal {
clearRegisteredReplacer(): void;
onContentRegistered(listener: AlertFlashContentRegistrationListener): () => void;
initialSyncRender(context: AlertFlashContentInitialContextRaw): AlertFlashContentInitialResult;
}

const nodeAsString = (node: ReactNode) =>
flattenChildren(node)

Check warning on line 72 in src/internal/plugins/controllers/alert-flash-content.ts

View check run for this annotation

Codecov / codecov/patch

src/internal/plugins/controllers/alert-flash-content.ts#L72

Added line #L72 was not covered by tests
.map(node => (typeof node === 'object' ? node.props.children : node))
.filter(node => typeof node === 'string')

Check warning on line 74 in src/internal/plugins/controllers/alert-flash-content.ts

View check run for this annotation

Codecov / codecov/patch

src/internal/plugins/controllers/alert-flash-content.ts#L74

Added line #L74 was not covered by tests
.join('');

export class AlertFlashContentController {
#listeners: Array<AlertFlashContentRegistrationListener> = [];
#cleanups = new Map<AlertFlashContentRegistrationListener, () => void>();
Expand Down Expand Up @@ -79,6 +107,21 @@ export class AlertFlashContentController {
this.#provider = undefined;
};

initialSyncRender = (context: AlertFlashContentInitialContextRaw): AlertFlashContentInitialResult => {
if (this.#provider?.initialCheck) {
const processedContext: AlertFlashContentInitialContext = {

Check warning on line 112 in src/internal/plugins/controllers/alert-flash-content.ts

View check run for this annotation

Codecov / codecov/patch

src/internal/plugins/controllers/alert-flash-content.ts#L112

Added line #L112 was not covered by tests
type: context.type,
headerText: nodeAsString(context.header),
contentText: nodeAsString(context.content),
};
return this.#provider.initialCheck(processedContext);

Check warning on line 117 in src/internal/plugins/controllers/alert-flash-content.ts

View check run for this annotation

Codecov / codecov/patch

src/internal/plugins/controllers/alert-flash-content.ts#L117

Added line #L117 was not covered by tests
}
return {
header: 'original',
content: 'original',
};
};

onContentRegistered = (listener: AlertFlashContentRegistrationListener) => {
if (this.#provider) {
const cleanup = listener(this.#provider);
Expand All @@ -102,6 +145,7 @@ export class AlertFlashContentController {
installInternal(internalApi: Partial<AlertFlashContentApiInternal> = {}): AlertFlashContentApiInternal {
internalApi.clearRegisteredReplacer ??= this.clearRegisteredReplacer;
internalApi.onContentRegistered ??= this.onContentRegistered;
internalApi.initialSyncRender ??= this.initialSyncRender;
return internalApi as AlertFlashContentApiInternal;
}
}
20 changes: 12 additions & 8 deletions src/internal/plugins/helpers/use-discovered-content.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,12 @@
import { ReactNode, useEffect, useRef, useState } from 'react';

import {
AlertFlashContentController,
AlertFlashContentApiInternal,
AlertFlashContentResult,
ReplacementType,
} from '../controllers/alert-flash-content';

export function createUseDiscoveredContent(
componentName: string,
onContentRegistered: AlertFlashContentController['onContentRegistered']
) {
export function createUseDiscoveredContent(componentName: string, controller: AlertFlashContentApiInternal) {
return function useDiscoveredContent({
type,
header,
Expand All @@ -25,14 +22,21 @@ export function createUseDiscoveredContent(
const contentRef = useRef<HTMLDivElement>(null);
const replacementHeaderRef = useRef<HTMLDivElement>(null);
const replacementContentRef = useRef<HTMLDivElement>(null);
const [headerReplacementType, setFoundHeaderReplacement] = useState<ReplacementType>('original');
const [contentReplacementType, setFoundContentReplacement] = useState<ReplacementType>('original');
const [initialState] = useState(() =>
controller.initialSyncRender({
type,
header,
content: children,
})
);
const [headerReplacementType, setFoundHeaderReplacement] = useState<ReplacementType>(initialState.header);
const [contentReplacementType, setFoundContentReplacement] = useState<ReplacementType>(initialState.content);
const mountedProvider = useRef<AlertFlashContentResult | undefined>();

useEffect(() => {
const context = { type, headerRef, contentRef };

return onContentRegistered(provider => {
return controller.onContentRegistered(provider => {
let mounted = true;

function checkMounted(methodName: string) {
Expand Down

0 comments on commit 31cf7ec

Please sign in to comment.