Power AppsのNotify関数が小さすぎる問題

# はじめに
`Power Apps`では[Notify 関数](https://learn.microsoft.com/ja-jp/power-platform/power-fx/reference/function-showerror)という、バナーメッセージを表示する関数があります。

コチラを使うことで、メッセージの種類に応じて、色とアイコンが使用されたメッセージの表示ができます。

https://learn.microsoft.com/ja-jp/power-platform/power-fx/reference/function-showerror

# これ小さすぎない？
[Notify 関数](https://learn.microsoft.com/ja-jp/power-platform/power-fx/reference/function-showerror)の弱点として、メッセージが小さいです。
実際の画面を見てみると、ほとんど見えない。特にPCでアプリケーションを使っている際には、ユーザーはポップアップに慣れている感覚があるためか、私の実体験として不評です。

![スクリーンショット 2024-12-22 141914.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2734332/dce85448-22ae-c059-7dab-1ea9a664f1b0.png)

:::note info
画面上部に表示されているバナーが[Notify 関数](https://learn.microsoft.com/ja-jp/power-platform/power-fx/reference/function-showerror)による表示です。
:::

![スクリーンショット 2024-12-22 141928.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2734332/a3dd2db8-08a6-5c55-40bf-aa1b06c9ff4d.png)

:::note info
タイムアウトを指定し、フェードアウトをさせられる便利な機能です。
:::

ブラウザで起動させたパターンですが、黄色警告のバナーのサイズもかなり小さい。

![スクリーンショット 2024-12-22 142147.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2734332/ad5c7302-3d07-3ed4-6e7f-8e089b5e9fce.png)

表示されているんだから見てください、というストロングスタイルもありますが、決してユーザー満足度が高くなるとは言い難いのではないでしょうか。

![スクリーンショット 2024-12-22 141933.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2734332/b0553ae3-c92a-0f55-45ca-7d06e41e4342.png)

## HTML文字列でNotifyを作る
対処方法として、HTML文字列で代替となる通知を自作する方法があります。
画面の最前面に`HTML 文字列`を用意し、下記の要素を入れてみるとしましょう。

**ℹ️ information**
ボタンコントロールで、HTML文字列を定義し、表示させます。
下記は`inforomation`のバージョンです。
アイコンは絵文字で代用しています。

![スクリーンショット 2024-12-22 141941.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2734332/dbe1f0e1-7ded-a073-85d7-25bc5b2613fc.png)

```javascript:OnSelect
UpdateContext(
    {
        ShowNotification: {
            type: "information",
            message: "Thanks for trying this!",
            html: "<div style='
                    background-color: #ebf5ff;
                    color: #1e429f;
                    padding: 12px;
                    border-radius: 6px;
                    border: 1px solid #c3dafe;
                    text-align: center;
                    position: relative;
                    font-size: 18px;
                    display: flex;
                    align-items: center;
                    justify-content: center;
                    gap: 8px;
                    animation: fadeOut 3s linear forwards;
                    '>
                    ℹ️ Thanks for trying this!
                    </div>"
        }
    }
);
UpdateContext({locNotifyVisible: true});
```

**⚠️ warning**

共通化する箇所は`App.Formulas`で定義することが可能です。

![スクリーンショット 2024-12-22 141958.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2734332/99ffaf8b-80f1-ec02-d078-efaf7a4fd1f2.png)

```javascript:OnSelect
UpdateContext(
    {
        ShowNotification: {
            type: "warning",
            message: "Please check your input!",
            html: "<div style='
                        background-color: #fff3cd;
                        color: #856404;
                        padding: 12px;
                        border-radius: 6px;
                        border: 1px solid #ffeeba;
                        text-align: center;
                        position: relative;
                        font-size: 18px;
                        display: flex;
                        align-items: center;
                        justify-content: center;
                        gap: 8px;
                        animation: fadeOut 3s linear forwards;
                    '>
                        ⚠️ Please check your input!
                    </div>"
        }
    }
);
UpdateContext({locNotifyVisible: true});
```

**✅ success**
`locNotifyVisible`によって、パーツの表示・非表示をコントロールします。

![スクリーンショット 2024-12-22 141951.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2734332/f9b7d0e0-3c17-b95c-310e-4e536132f9c0.png)

```javascript:OnSelect
UpdateContext(
    {
        ShowNotification: {
            type: "success",
            message: "Operation completed successfully!",
            html: "<div style='
                    background-color: #d4edda;
                    color: #155724;
                    padding: 12px;
                    border-radius: 6px;
                    border: 1px solid #c3e6cb;
                    text-align: center;
                    position: relative;
                    font-size: 18px;
                    display: flex;
                    align-items: center;
                    justify-content: center;
                    gap: 8px;
                    animation: fadeOut 3s linear forwards;
                '>
                    ✅ Operation completed successfully!
                </div>"
        }
    }
);
UpdateContext({locNotifyVisible: true});
```

**❌ error**
`info`・`warning`・`success`・`error`ほぼ共通です。
プレビュー段階のため避けていますが、再利用できる関数で作成できることが理想的ですね。

![スクリーンショット 2024-12-22 142004.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2734332/a3c10411-8554-1cb4-fb6b-9799eef85e31.png)

```javascript:OnSelect
UpdateContext(
    {
        ShowNotification: {
            type: "error",
            message: "An error has occurred!",
            html: "<div style='
                        background-color: #f8d7da;
                        color: #721c24;
                        padding: 12px;
                        border-radius: 6px;
                        border: 1px solid #f5c6cb;
                        text-align: center;
                        position: relative;
                        font-size: 18px;
                        display: flex;
                        align-items: center;
                        justify-content: center;
                        gap: 8px;
                        animation: fadeOut 3s linear forwards;
                    '>
                        ❌ An error has occurred!
                    </div>"
        }
    }
);
UpdateContext({locNotifyVisible: true});
```

# ポップアップを自作する
次に`PopUp`を`HTML`文字列で再現してみましょう。
`Yes`・`No`のハンドリングは発生しないあくまで一時的な通知として使う想定で作成します。

関連する要素が多くなるため、定型的な文字列は、`App.Formulas`で設定してしまいます。
変動しない要素は、コチラで設定してしまうことがおススメです。

```javascript:App.Formulas
popupColors = {
    info: {
        primary: "#4A90E2",
        icon: "ℹ️"
    },
    success: {
        primary: "#28a745",
        icon: "✅"
    },
    warning: {
        primary: "#ffc107",
        icon: "⚠️"
    },
    error: {
        primary: "#dc3545",
        icon: "❌"
    },
    overlay: "rgba(0, 0, 0, 0.5)",
    white: "white",
    text: "#333",
    close: "#666"
};
popupSizes = {
    width: "50vw",
    maxWidth: "500px",
    minWidth: "300px",
    height: "200px",
    accentWidth: "6px",
    padding: "1.5rem",
    borderRadius: "8px"
};
popupIcon = {
    size: "18px",
    fontSize: "12px"
};
```

**ℹ️ information**
ポップアップのイメージです。あくまでなんちゃってポップアップです。

![スクリーンショット 2024-12-22 142014.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2734332/1318a923-1376-f559-e699-5c2ad068f023.png)

```javascript:OnSelect
UpdateContext(
    {
        currentPopup: Switch(
            Self.Text,
            "Info",
            {
                primary: popupColors.info.primary,
                icon: popupColors.info.icon,
                title: "確認",
                content: "このアクションを実行してもよろしいですか？"
            },
            "Success",
            {
                primary: popupColors.success.primary,
                icon: popupColors.success.icon,
                title: "完了",
                content: "アクションは正常に完了しました。"
            },
            "Warn",
            {
                primary: popupColors.warning.primary,
                icon: popupColors.warning.icon,
                title: "警告",
                content: "このアクションは取り消せません。続行しますか？"
            },
            "Error",
            {
                primary: popupColors.error.primary,
                icon: popupColors.error.icon,
                title: "エラー",
                content: "エラーが発生しました。もう一度お試しください。"
            }
        )
    }
);
UpdateContext(
    {
        popupStyles: {
            overlay: 
                $"position: fixed; top: 0; left: 0; width: 100%; height: 100%; background-color: {popupColors.overlay}; display: flex; justify-content: center; align-items: center;",
            container: 
                $"background-color: {popupColors.white}; padding: {popupSizes.padding}; border-radius: {popupSizes.borderRadius}; box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1); width: {popupSizes.width}; max-width: {popupSizes.maxWidth}; min-width: {popupSizes.minWidth}; height: {popupSizes.height}; position: relative; display: flex;",
            accentLine: 
                $"position: absolute; left: 0; top: 0; width: {popupSizes.accentWidth}; height: 100%; background-color: {currentPopup.primary}; border-top-left-radius: {popupSizes.borderRadius}; border-bottom-left-radius: {popupSizes.borderRadius};",
            contentWrapper: 
                $"flex-grow: 1; padding-left: 1rem; display: flex; flex-direction: column;",
            header: $"margin-bottom: 1rem;",
            title: $"font-size: 1.125rem; font-weight: 600; margin: 0; color: {popupColors.text}; display: flex; align-items: center; gap: 0.5rem;",
            infoIcon: $"display: flex; align-items: center; justify-content: center; width: {popupIcon.size}; height: {popupIcon.size}; solid {currentPopup.primary}; border-radius: 50%; color: {currentPopup.primary}; font-size: {popupIcon.fontSize}; font-weight: bold; flex-shrink: 0;",
            content: $"flex-grow: 1; display: flex; align-items: center; font-size: 0.975rem;",
            buttonArea: $"display: flex; justify-content: flex-end; padding-top: 1rem;",
            button: 
                $"padding: 0.5rem 2rem; border-radius: 4px; border: none; cursor: pointer; font-weight: 500; background-color: {currentPopup.primary}; color: {popupColors.white}; min-width: 100px;",
            closeButton: 
                $"position: absolute; top: 1rem; right: 1rem; width: 24px; height: 24px; border: none; background: none; cursor: pointer; padding: 0; display: flex; align-items: center; justify-content: center;",
            closeLine: $"position: absolute; width: 2px; height: 16px; background-color: {popupColors.close};"
        },
        popupTexts: {
            title: currentPopup.title,
            content: currentPopup.content,
            button: "Yes",
            close: "閉じる",
            info: currentPopup.icon
        }
    }
);
UpdateContext({locPopupVisible: true});
```

:::note info
`ポップアップ`の`OnSelect`に`UpdateContext({locPopupVisible: true});`を設定して通知を消します。
つまりボタンでもどこでもクリックすれば通知が消えるという仕様です。
クリックされたコントロールによって操作をわけるためには、コンポーネントで自作する方法が挙げられます。
:::

**⚠️ warning**
`Switch(Self.Text・・・`は、ボタンコントロールのテキスト`Info`・`Success`・`Warning`・`Error`から設定する値を定義を分けています。

https://learn.microsoft.com/ja-jp/power-platform/power-fx/reference/function-if

![スクリーンショット 2024-12-22 142021.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2734332/1b6c21c1-f74c-e226-bb24-8594dfb963dc.png)

```javascript:OnSelect
UpdateContext(
    {
        currentPopup: Switch(
            Self.Text,
            "Info",
            {
                primary: popupColors.info.primary,
                icon: popupColors.info.icon,
                title: "確認",
                content: "このアクションを実行してもよろしいですか？"
            },
            "Success",
            {
                primary: popupColors.success.primary,
                icon: popupColors.success.icon,
                title: "完了",
                content: "アクションは正常に完了しました。"
            },
            "Warning",
            {
                primary: popupColors.warning.primary,
                icon: popupColors.warning.icon,
                title: "警告",
                content: "このアクションは取り消せません。続行しますか？"
            },
            "Error",
            {
                primary: popupColors.error.primary,
                icon: popupColors.error.icon,
                title: "エラー",
                content: "エラーが発生しました。もう一度お試しください。"
            }
        )
    }
);
UpdateContext(
    {
        popupStyles: {
            overlay: 
                $"position: fixed; top: 0; left: 0; width: 100%; height: 100%; background-color: {popupColors.overlay}; display: flex; justify-content: center; align-items: center;",
            container: 
                $"background-color: {popupColors.white}; padding: {popupSizes.padding}; border-radius: {popupSizes.borderRadius}; box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1); width: {popupSizes.width}; max-width: {popupSizes.maxWidth}; min-width: {popupSizes.minWidth}; height: {popupSizes.height}; position: relative; display: flex;",
            accentLine: 
                $"position: absolute; left: 0; top: 0; width: {popupSizes.accentWidth}; height: 100%; background-color: {currentPopup.primary}; border-top-left-radius: {popupSizes.borderRadius}; border-bottom-left-radius: {popupSizes.borderRadius};",
            contentWrapper: 
                $"flex-grow: 1; padding-left: 1rem; display: flex; flex-direction: column;",
            header: $"margin-bottom: 1rem;",
            title: $"font-size: 1.125rem; font-weight: 600; margin: 0; color: {popupColors.text}; display: flex; align-items: center; gap: 0.5rem;",
            infoIcon: $"display: flex; align-items: center; justify-content: center; width: {popupIcon.size}; height: {popupIcon.size}; solid {currentPopup.primary}; border-radius: 50%; color: {currentPopup.primary}; font-size: {popupIcon.fontSize}; font-weight: bold; flex-shrink: 0;",
            content: $"flex-grow: 1; display: flex; align-items: center; font-size: 0.975rem;",
            buttonArea: $"display: flex; justify-content: flex-end; padding-top: 1rem;",
            button: 
                $"padding: 0.5rem 2rem; border-radius: 4px; border: none; cursor: pointer; font-weight: 500; background-color: {currentPopup.primary}; color: {popupColors.white}; min-width: 100px;",
            closeButton: 
                $"position: absolute; top: 1rem; right: 1rem; width: 24px; height: 24px; border: none; background: none; cursor: pointer; padding: 0; display: flex; align-items: center; justify-content: center;",
            closeLine: $"position: absolute; width: 2px; height: 16px; background-color: {popupColors.close};"
        },
        popupTexts: {
            title: currentPopup.title,
            content: currentPopup.content,
            button: "Yes",
            close: "閉じる",
            info: currentPopup.icon
        }
    }
);
UpdateContext({locPopupVisible: true});
```

**✅ success**

![スクリーンショット 2024-12-22 142037.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2734332/d6c4c7f0-2a45-1537-d7a1-f123e7658d5e.png)

```javascript:OnSelect
UpdateContext(
    {
        currentPopup: Switch(
            Self.Text,
            "Info",
            {
                primary: popupColors.info.primary,
                icon: popupColors.info.icon,
                title: "確認",
                content: "このアクションを実行してもよろしいですか？"
            },
            "Success",
            {
                primary: popupColors.success.primary,
                icon: popupColors.success.icon,
                title: "完了",
                content: "アクションは正常に完了しました。"
            },
            "Warn",
            {
                primary: popupColors.warning.primary,
                icon: popupColors.warning.icon,
                title: "警告",
                content: "このアクションは取り消せません。続行しますか？"
            },
            "Error",
            {
                primary: popupColors.error.primary,
                icon: popupColors.error.icon,
                title: "エラー",
                content: "エラーが発生しました。もう一度お試しください。"
            }
        )
    }
);
UpdateContext(
    {
        popupStyles: {
            overlay: 
                $"position: fixed; top: 0; left: 0; width: 100%; height: 100%; background-color: {popupColors.overlay}; display: flex; justify-content: center; align-items: center;",
            container: 
                $"background-color: {popupColors.white}; padding: {popupSizes.padding}; border-radius: {popupSizes.borderRadius}; box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1); width: {popupSizes.width}; max-width: {popupSizes.maxWidth}; min-width: {popupSizes.minWidth}; height: {popupSizes.height}; position: relative; display: flex;",
            accentLine: 
                $"position: absolute; left: 0; top: 0; width: {popupSizes.accentWidth}; height: 100%; background-color: {currentPopup.primary}; border-top-left-radius: {popupSizes.borderRadius}; border-bottom-left-radius: {popupSizes.borderRadius};",
            contentWrapper: 
                $"flex-grow: 1; padding-left: 1rem; display: flex; flex-direction: column;",
            header: $"margin-bottom: 1rem;",
            title: $"font-size: 1.125rem; font-weight: 600; margin: 0; color: {popupColors.text}; display: flex; align-items: center; gap: 0.5rem;",
            infoIcon: $"display: flex; align-items: center; justify-content: center; width: {popupIcon.size}; height: {popupIcon.size}; solid {currentPopup.primary}; border-radius: 50%; color: {currentPopup.primary}; font-size: {popupIcon.fontSize}; font-weight: bold; flex-shrink: 0;",
            content: $"flex-grow: 1; display: flex; align-items: center; font-size: 0.975rem;",
            buttonArea: $"display: flex; justify-content: flex-end; padding-top: 1rem;",
            button: 
                $"padding: 0.5rem 2rem; border-radius: 4px; border: none; cursor: pointer; font-weight: 500; background-color: {currentPopup.primary}; color: {popupColors.white}; min-width: 100px;",
            closeButton: 
                $"position: absolute; top: 1rem; right: 1rem; width: 24px; height: 24px; border: none; background: none; cursor: pointer; padding: 0; display: flex; align-items: center; justify-content: center;",
            closeLine: $"position: absolute; width: 2px; height: 16px; background-color: {popupColors.close};"
        },
        popupTexts: {
            title: currentPopup.title,
            content: currentPopup.content,
            button: "Yes",
            close: "閉じる",
            info: currentPopup.icon
        }
    }
);
UpdateContext({locPopupVisible: true});
```

**❌ error**

![スクリーンショット 2024-12-22 142046.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2734332/b20be470-2dec-b31a-e1b6-81e34ee08305.png)

```javascript:OnSelect
UpdateContext(
    {
        currentPopup: Switch(
            Self.Text,
            "Info",
            {
                primary: popupColors.info.primary,
                icon: popupColors.info.icon,
                title: "確認",
                content: "このアクションを実行してもよろしいですか？"
            },
            "Success",
            {
                primary: popupColors.success.primary,
                icon: popupColors.success.icon,
                title: "完了",
                content: "アクションは正常に完了しました。"
            },
            "Warn",
            {
                primary: popupColors.warning.primary,
                icon: popupColors.warning.icon,
                title: "警告",
                content: "このアクションは取り消せません。続行しますか？"
            },
            "Error",
            {
                primary: popupColors.error.primary,
                icon: popupColors.error.icon,
                title: "エラー",
                content: "エラーが発生しました。もう一度お試しください。"
            }
        )
    }
);
UpdateContext(
    {
        popupStyles: {
            overlay: 
                $"position: fixed; top: 0; left: 0; width: 100%; height: 100%; background-color: {popupColors.overlay}; display: flex; justify-content: center; align-items: center;",
            container: 
                $"background-color: {popupColors.white}; padding: {popupSizes.padding}; border-radius: {popupSizes.borderRadius}; box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1); width: {popupSizes.width}; max-width: {popupSizes.maxWidth}; min-width: {popupSizes.minWidth}; height: {popupSizes.height}; position: relative; display: flex;",
            accentLine: 
                $"position: absolute; left: 0; top: 0; width: {popupSizes.accentWidth}; height: 100%; background-color: {currentPopup.primary}; border-top-left-radius: {popupSizes.borderRadius}; border-bottom-left-radius: {popupSizes.borderRadius};",
            contentWrapper: 
                $"flex-grow: 1; padding-left: 1rem; display: flex; flex-direction: column;",
            header: $"margin-bottom: 1rem;",
            title: $"font-size: 1.125rem; font-weight: 600; margin: 0; color: {popupColors.text}; display: flex; align-items: center; gap: 0.5rem;",
            infoIcon: $"display: flex; align-items: center; justify-content: center; width: {popupIcon.size}; height: {popupIcon.size}; solid {currentPopup.primary}; border-radius: 50%; color: {currentPopup.primary}; font-size: {popupIcon.fontSize}; font-weight: bold; flex-shrink: 0;",
            content: $"flex-grow: 1; display: flex; align-items: center; font-size: 0.975rem;",
            buttonArea: $"display: flex; justify-content: flex-end; padding-top: 1rem;",
            button: 
                $"padding: 0.5rem 2rem; border-radius: 4px; border: none; cursor: pointer; font-weight: 500; background-color: {currentPopup.primary}; color: {popupColors.white}; min-width: 100px;",
            closeButton: 
                $"position: absolute; top: 1rem; right: 1rem; width: 24px; height: 24px; border: none; background: none; cursor: pointer; padding: 0; display: flex; align-items: center; justify-content: center;",
            closeLine: $"position: absolute; width: 2px; height: 16px; background-color: {popupColors.close};"
        },
        popupTexts: {
            title: currentPopup.title,
            content: currentPopup.content,
            button: "Yes",
            close: "閉じる",
            info: currentPopup.icon
        }
    }
);
UpdateContext({locPopupVisible: true});
```

# おわりに
より本格的なものを参照したい場合は下記のサイトがおススメです。
Power AppsのYAML ソースコードが参照できます。

https://coda.io/@mycondy/power-apps-ui-cooker/info-popup-with-button-45

https://coda.io/@mycondy/power-apps-ui-cooker