aboutsummaryrefslogtreecommitdiff
path: root/packages/web-util/src/components
diff options
context:
space:
mode:
authorSebastian <sebasjm@gmail.com>2023-11-05 18:04:22 -0300
committerSebastian <sebasjm@gmail.com>2023-11-05 18:04:22 -0300
commitb58d53dd93bd8e97aecc28fae788c5c7051fd73d (patch)
tree7a402fafa4ae19a64f10eeb3042147f5f4733081 /packages/web-util/src/components
parent31cf3187e447e2c4ec8a473362c5bacc07a874f1 (diff)
downloadwallet-core-b58d53dd93bd8e97aecc28fae788c5c7051fd73d.tar.xz
sharing components in web-util
Diffstat (limited to 'packages/web-util/src/components')
-rw-r--r--packages/web-util/src/components/Attention.tsx58
-rw-r--r--packages/web-util/src/components/CopyButton.tsx46
-rw-r--r--packages/web-util/src/components/ErrorLoading.tsx119
-rw-r--r--packages/web-util/src/components/LangSelector.tsx111
-rw-r--r--packages/web-util/src/components/Loading.tsx45
-rw-r--r--packages/web-util/src/components/ShowInputErrorLabel.tsx29
-rw-r--r--packages/web-util/src/components/ShowLocalNotification.tsx43
-rw-r--r--packages/web-util/src/components/index.ts7
8 files changed, 458 insertions, 0 deletions
diff --git a/packages/web-util/src/components/Attention.tsx b/packages/web-util/src/components/Attention.tsx
new file mode 100644
index 000000000..b85230a1b
--- /dev/null
+++ b/packages/web-util/src/components/Attention.tsx
@@ -0,0 +1,58 @@
+import { TranslatedString, assertUnreachable } from "@gnu-taler/taler-util";
+import { ComponentChildren, Fragment, VNode, h } from "preact";
+
+interface Props {
+ type?: "info" | "success" | "warning" | "danger",
+ onClose?: () => void,
+ title: TranslatedString,
+ children?: ComponentChildren,
+}
+export function Attention({ type = "info", title, children, onClose }: Props): VNode {
+ return <div class={`group attention-${type} mt-2 shadow-lg`}>
+ <div class="rounded-md group-[.attention-info]:bg-blue-50 group-[.attention-warning]:bg-yellow-50 group-[.attention-danger]:bg-red-50 group-[.attention-success]:bg-green-50 p-4 shadow">
+ <div class="flex">
+ <div >
+ <svg xmlns="http://www.w3.org/2000/svg" stroke="none" viewBox="0 0 24 24" fill="currentColor" class="w-8 h-8 group-[.attention-info]:text-blue-400 group-[.attention-warning]:text-yellow-400 group-[.attention-danger]:text-red-400 group-[.attention-success]:text-green-400">
+ {(() => {
+ switch (type) {
+ case "info":
+ return <path fill-rule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a.75.75 0 000 1.5h.253a.25.25 0 01.244.304l-.459 2.066A1.75 1.75 0 0010.747 15H11a.75.75 0 000-1.5h-.253a.25.25 0 01-.244-.304l.459-2.066A1.75 1.75 0 009.253 9H9z" />
+ case "warning":
+ return <path fill-rule="evenodd" d="M9.401 3.003c1.155-2 4.043-2 5.197 0l7.355 12.748c1.154 2-.29 4.5-2.599 4.5H4.645c-2.309 0-3.752-2.5-2.598-4.5L9.4 3.003zM12 8.25a.75.75 0 01.75.75v3.75a.75.75 0 01-1.5 0V9a.75.75 0 01.75-.75zm0 8.25a.75.75 0 100-1.5.75.75 0 000 1.5z" />
+ case "danger":
+ return <path fill-rule="evenodd" d="M2.25 12c0-5.385 4.365-9.75 9.75-9.75s9.75 4.365 9.75 9.75-4.365 9.75-9.75 9.75S2.25 17.385 2.25 12zM12 8.25a.75.75 0 01.75.75v3.75a.75.75 0 01-1.5 0V9a.75.75 0 01.75-.75zm0 8.25a.75.75 0 100-1.5.75.75 0 000 1.5z" />
+ case "success":
+ return <path fill-rule="evenodd" d="M7.493 18.75c-.425 0-.82-.236-.975-.632A7.48 7.48 0 016 15.375c0-1.75.599-3.358 1.602-4.634.151-.192.373-.309.6-.397.473-.183.89-.514 1.212-.924a9.042 9.042 0 012.861-2.4c.723-.384 1.35-.956 1.653-1.715a4.498 4.498 0 00.322-1.672V3a.75.75 0 01.75-.75 2.25 2.25 0 012.25 2.25c0 1.152-.26 2.243-.723 3.218-.266.558.107 1.282.725 1.282h3.126c1.026 0 1.945.694 2.054 1.715.045.422.068.85.068 1.285a11.95 11.95 0 01-2.649 7.521c-.388.482-.987.729-1.605.729H14.23c-.483 0-.964-.078-1.423-.23l-3.114-1.04a4.501 4.501 0 00-1.423-.23h-.777zM2.331 10.977a11.969 11.969 0 00-.831 4.398 12 12 0 00.52 3.507c.26.85 1.084 1.368 1.973 1.368H4.9c.445 0 .72-.498.523-.898a8.963 8.963 0 01-.924-3.977c0-1.708.476-3.305 1.302-4.666.245-.403-.028-.959-.5-.959H4.25c-.832 0-1.612.453-1.918 1.227z" />
+ default:
+ assertUnreachable(type)
+ }
+ })()}
+ </svg>
+ </div>
+ <div class="ml-3 w-full">
+ <h3 class="text-sm group-hover:text-white font-bold group-[.attention-info]:text-blue-800 group-[.attention-success]:text-green-800 group-[.attention-warning]:text-yellow-800 group-[.attention-danger]:text-red-800">
+ {title}
+ </h3>
+ <div class="mt-2 text-sm group-[.attention-info]:text-blue-700 group-[.attention-warning]:text-yellow-700 group-[.attention-danger]:text-red-700 group-[.attention-success]:text-green-700">
+ {children}
+ </div>
+ </div>
+ {onClose &&
+ <div>
+ <button type="button" class="font-semibold items-center rounded bg-transparent px-2 py-1 text-xs text-gray-900 hover:bg-gray-50"
+ onClick={(e) => {
+ e.preventDefault();
+ onClose();
+ }}
+ >
+ <svg class="h-5 w-5" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
+ <path d="M6.28 5.22a.75.75 0 00-1.06 1.06L8.94 10l-3.72 3.72a.75.75 0 101.06 1.06L10 11.06l3.72 3.72a.75.75 0 101.06-1.06L11.06 10l3.72-3.72a.75.75 0 00-1.06-1.06L10 8.94 6.28 5.22z" />
+ </svg>
+ </button>
+ </div>
+ }
+ </div>
+ </div>
+
+ </div>
+}
diff --git a/packages/web-util/src/components/CopyButton.tsx b/packages/web-util/src/components/CopyButton.tsx
new file mode 100644
index 000000000..0096da365
--- /dev/null
+++ b/packages/web-util/src/components/CopyButton.tsx
@@ -0,0 +1,46 @@
+import { h, VNode } from "preact";
+import { useEffect, useState } from "preact/hooks";
+
+export function CopyIcon(): VNode {
+ return (
+ <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6">
+ <path stroke-linecap="round" stroke-linejoin="round" d="M15.75 17.25v3.375c0 .621-.504 1.125-1.125 1.125h-9.75a1.125 1.125 0 01-1.125-1.125V7.875c0-.621.504-1.125 1.125-1.125H6.75a9.06 9.06 0 011.5.124m7.5 10.376h3.375c.621 0 1.125-.504 1.125-1.125V11.25c0-4.46-3.243-8.161-7.5-8.876a9.06 9.06 0 00-1.5-.124H9.375c-.621 0-1.125.504-1.125 1.125v3.5m7.5 10.375H9.375a1.125 1.125 0 01-1.125-1.125v-9.25m12 6.625v-1.875a3.375 3.375 0 00-3.375-3.375h-1.5a1.125 1.125 0 01-1.125-1.125v-1.5a3.375 3.375 0 00-3.375-3.375H9.75" />
+ </svg>
+ )
+};
+
+export function CopiedIcon(): VNode {
+ return (
+ <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6">
+ <path stroke-linecap="round" stroke-linejoin="round" d="M4.5 12.75l6 6 9-13.5" />
+ </svg>
+ )
+};
+
+export function CopyButton({ class: clazz, getContent }: { class: string, getContent: () => string }): VNode {
+ const [copied, setCopied] = useState(false);
+ function copyText(): void {
+ navigator.clipboard.writeText(getContent() || "");
+ setCopied(true);
+ }
+ useEffect(() => {
+ if (copied) {
+ setTimeout(() => {
+ setCopied(false);
+ }, 1000);
+ }
+ }, [copied]);
+
+ if (!copied) {
+ return (
+ <button class={clazz} onClick={copyText} >
+ <CopyIcon />
+ </button>
+ );
+ }
+ return (
+ <button class={clazz} disabled>
+ <CopiedIcon />
+ </button>
+ );
+}
diff --git a/packages/web-util/src/components/ErrorLoading.tsx b/packages/web-util/src/components/ErrorLoading.tsx
new file mode 100644
index 000000000..02f2a3282
--- /dev/null
+++ b/packages/web-util/src/components/ErrorLoading.tsx
@@ -0,0 +1,119 @@
+/*
+/*
+ This file is part of GNU Taler
+ (C) 2022 Taler Systems S.A.
+
+ GNU Taler is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+
+import { TalerError, TalerErrorCode, assertUnreachable } from "@gnu-taler/taler-util";
+import { Fragment, VNode, h } from "preact";
+import { Attention } from "./Attention.js";
+import { useTranslationContext } from "../index.browser.js";
+
+export function ErrorLoading({ error, showDetail }: { error: TalerError, showDetail?: boolean }): VNode {
+ const { i18n } = useTranslationContext()
+ switch (error.errorDetail.code) {
+ //////////////////
+ // Every error that can be produce in a Http Request
+ //////////////////
+ case TalerErrorCode.WALLET_HTTP_REQUEST_GENERIC_TIMEOUT: {
+ if (error.hasErrorCode(TalerErrorCode.WALLET_HTTP_REQUEST_GENERIC_TIMEOUT)) {
+ const { requestMethod, requestUrl, timeoutMs } = error.errorDetail
+ return <Attention type="danger" title={i18n.str`The request reached a timeout, check your connection.`}>
+ {error.message}
+ {showDetail &&
+ <pre class="whitespace-break-spaces ">
+ {JSON.stringify({ requestMethod, requestUrl, timeoutMs }, undefined, 2)}
+ </pre>
+ }
+ </Attention>
+ }
+ assertUnreachable(1 as never)
+ }
+ case TalerErrorCode.WALLET_HTTP_REQUEST_THROTTLED: {
+ if (error.hasErrorCode(TalerErrorCode.WALLET_HTTP_REQUEST_THROTTLED)) {
+ const { requestMethod, requestUrl, throttleStats } = error.errorDetail
+ return <Attention type="danger" title={i18n.str`A lot of request were made to the same server and this action was throttled`}>
+ {error.message}
+ {showDetail &&
+ <pre class="whitespace-break-spaces ">
+ {JSON.stringify({ requestMethod, requestUrl, throttleStats }, undefined, 2)}
+ </pre>
+ }
+ </Attention>
+ }
+ assertUnreachable(1 as never)
+ }
+ case TalerErrorCode.WALLET_RECEIVED_MALFORMED_RESPONSE: {
+ if (error.hasErrorCode(TalerErrorCode.WALLET_RECEIVED_MALFORMED_RESPONSE)) {
+ const { requestMethod, requestUrl, httpStatusCode, validationError } = error.errorDetail
+ return <Attention type="danger" title={i18n.str`The response of the request is malformed.`}>
+ {error.message}
+ {showDetail &&
+ <pre class="whitespace-break-spaces ">
+ {JSON.stringify({ requestMethod, requestUrl, httpStatusCode, validationError }, undefined, 2)}
+ </pre>
+ }
+ </Attention>
+ }
+ assertUnreachable(1 as never)
+ }
+ case TalerErrorCode.WALLET_NETWORK_ERROR: {
+ if (error.hasErrorCode(TalerErrorCode.WALLET_NETWORK_ERROR)) {
+ const { requestMethod, requestUrl } = error.errorDetail
+ return <Attention type="danger" title={i18n.str`Could not complete the request due to a network problem.`}>
+ {error.message}
+ {showDetail &&
+ <pre class="whitespace-break-spaces ">
+ {JSON.stringify({ requestMethod, requestUrl }, undefined, 2)}
+ </pre>
+ }
+ </Attention>
+ }
+ assertUnreachable(1 as never)
+ }
+ case TalerErrorCode.WALLET_UNEXPECTED_REQUEST_ERROR: {
+ if (error.hasErrorCode(TalerErrorCode.WALLET_UNEXPECTED_REQUEST_ERROR)) {
+ const { requestMethod, requestUrl, httpStatusCode, errorResponse } = error.errorDetail
+ return <Attention type="danger" title={i18n.str`Unexpected request error`}>
+ {error.message}
+ {showDetail &&
+ <pre class="whitespace-break-spaces ">
+ {JSON.stringify({ requestMethod, requestUrl, httpStatusCode, errorResponse }, undefined, 2)}
+ </pre>
+ }
+ </Attention>
+ }
+ assertUnreachable(1 as never)
+ }
+ //////////////////
+ // Every other error
+ //////////////////
+ // case TalerErrorCode.WALLET_UNEXPECTED_REQUEST_ERROR: {
+ // return <Attention type="danger" title={i18n.str``}>
+ // </Attention>
+ // }
+ //////////////////
+ // Default message for unhandled case
+ //////////////////
+ default: return <Attention type="danger" title={i18n.str`Unexpected error`}>
+ {error.message}
+ {showDetail &&
+ <pre class="whitespace-break-spaces ">
+ {JSON.stringify(error.errorDetail, undefined, 2)}
+ </pre>
+ }
+ </Attention>
+ }
+}
+
diff --git a/packages/web-util/src/components/LangSelector.tsx b/packages/web-util/src/components/LangSelector.tsx
new file mode 100644
index 000000000..a8d910129
--- /dev/null
+++ b/packages/web-util/src/components/LangSelector.tsx
@@ -0,0 +1,111 @@
+/*
+ This file is part of GNU Taler
+ (C) 2022 Taler Systems S.A.
+
+ GNU Taler is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+
+/**
+ *
+ * @author Sebastian Javier Marchano (sebasjm)
+ */
+
+import { Fragment, h, VNode } from "preact";
+import { useEffect, useState } from "preact/hooks";
+// import { strings as messages } from "../i18n/strings.js";
+import langIcon from "../assets/lang.svg";
+import { useTranslationContext } from "../index.browser.js";
+
+type LangsNames = {
+ [P: string]: string;
+};
+
+const names: LangsNames = {
+ es: "Español [es]",
+ en: "English [en]",
+ fr: "Français [fr]",
+ de: "Deutsch [de]",
+ sv: "Svenska [sv]",
+ it: "Italiano [it]",
+};
+
+function getLangName(s: keyof LangsNames | string): string {
+ if (names[s]) return names[s];
+ return String(s);
+}
+
+export function LangSelector({ supportedLangs }: { supportedLangs: string[] }): VNode {
+ const [updatingLang, setUpdatingLang] = useState(false);
+ const { lang, changeLanguage } = useTranslationContext();
+ const [hidden, setHidden] = useState(true);
+
+ useEffect(() => {
+ function bodyKeyPress(event: KeyboardEvent) {
+ if (event.code === "Escape") setHidden(true);
+ }
+ function bodyOnClick(event: Event) {
+ setHidden(true);
+ }
+ document.body.addEventListener("click", bodyOnClick);
+ document.body.addEventListener("keydown", bodyKeyPress as any);
+ return () => {
+ document.body.removeEventListener("keydown", bodyKeyPress as any);
+ document.body.removeEventListener("click", bodyOnClick);
+ };
+ }, []);
+ return (
+ <div>
+ <div class="relative mt-2">
+ <button type="button" class="relative w-full cursor-default rounded-md bg-white py-1.5 pl-3 pr-10 text-left text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 focus:outline-none focus:ring-2 focus:ring-indigo-600 sm:text-sm sm:leading-6" aria-haspopup="listbox" aria-expanded="true" aria-labelledby="listbox-label"
+ onClick={() => {
+ setHidden((h) => !h);
+ }}>
+ <span class="flex items-center">
+ <img alt="language" class="h-5 w-5 flex-shrink-0 rounded-full" src={langIcon} />
+ <span class="ml-3 block truncate">{getLangName(lang)}</span>
+ </span>
+ <span class="pointer-events-none absolute inset-y-0 right-0 flex items-center pr-2">
+ <svg class="h-5 w-5 text-gray-400" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
+ <path fill-rule="evenodd" d="M10 3a.75.75 0 01.55.24l3.25 3.5a.75.75 0 11-1.1 1.02L10 4.852 7.3 7.76a.75.75 0 01-1.1-1.02l3.25-3.5A.75.75 0 0110 3zm-3.76 9.2a.75.75 0 011.06.04l2.7 2.908 2.7-2.908a.75.75 0 111.1 1.02l-3.25 3.5a.75.75 0 01-1.1 0l-3.25-3.5a.75.75 0 01.04-1.06z" clip-rule="evenodd" />
+ </svg>
+ </span>
+ </button>
+
+ {!hidden &&
+ <ul class="absolute z-10 mt-1 max-h-60 w-full overflow-auto rounded-md bg-white py-1 text-base shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none sm:text-sm" tabIndex={-1} role="listbox" aria-labelledby="listbox-label" aria-activedescendant="listbox-option-3">
+ {supportedLangs
+ .filter((l) => l !== lang)
+ .map((lang) => (
+ <li class="text-gray-900 hover:bg-indigo-600 hover:text-white cursor-pointer relative select-none py-2 pl-3 pr-9" role="option"
+ onClick={() => {
+ changeLanguage(lang);
+ setUpdatingLang(false);
+ setHidden(true)
+ }}
+ >
+ <span class="font-normal block truncate">{getLangName(lang)}</span>
+
+ <span class="text-indigo-600 absolute inset-y-0 right-0 flex items-center pr-4">
+ {/* <svg class="h-5 w-5" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
+ <path fill-rule="evenodd" d="M16.704 4.153a.75.75 0 01.143 1.052l-8 10.5a.75.75 0 01-1.127.075l-4.5-4.5a.75.75 0 011.06-1.06l3.894 3.893 7.48-9.817a.75.75 0 011.05-.143z" clip-rule="evenodd" />
+ </svg> */}
+ </span>
+ </li>
+ ))}
+
+ </ul>
+ }
+
+ </div>
+ </div>
+ );
+}
diff --git a/packages/web-util/src/components/Loading.tsx b/packages/web-util/src/components/Loading.tsx
new file mode 100644
index 000000000..b567e9056
--- /dev/null
+++ b/packages/web-util/src/components/Loading.tsx
@@ -0,0 +1,45 @@
+/*
+ This file is part of GNU Taler
+ (C) 2022 Taler Systems S.A.
+
+ GNU Taler is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+
+import { h, VNode } from "preact";
+
+export function Loading(): VNode {
+ return (
+ <div
+ class="columns is-centered is-vcentered"
+ style={{
+ width: "100%",
+ height: "200px",
+ display: "flex",
+ margin: "auto",
+ justifyContent: "center",
+ }}
+ >
+ <Spinner />
+ </div>
+ );
+}
+
+export function Spinner(): VNode {
+ return (
+ <div class="lds-ring" style={{margin:"auto"}}>
+ <div />
+ <div />
+ <div />
+ <div />
+ </div>
+ );
+}
diff --git a/packages/web-util/src/components/ShowInputErrorLabel.tsx b/packages/web-util/src/components/ShowInputErrorLabel.tsx
new file mode 100644
index 000000000..c5840cad9
--- /dev/null
+++ b/packages/web-util/src/components/ShowInputErrorLabel.tsx
@@ -0,0 +1,29 @@
+/*
+ This file is part of GNU Taler
+ (C) 2022 Taler Systems S.A.
+
+ GNU Taler is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+
+import { Fragment, h, VNode } from "preact";
+
+export function ShowInputErrorLabel({
+ isDirty,
+ message,
+}: {
+ message: string | undefined;
+ isDirty: boolean;
+}): VNode {
+ if (message && isDirty)
+ return <div class="text-base" style={{ color: "red" }}>{message}</div>;
+ return <div class="text-base" style={{ }}> </div>;
+}
diff --git a/packages/web-util/src/components/ShowLocalNotification.tsx b/packages/web-util/src/components/ShowLocalNotification.tsx
new file mode 100644
index 000000000..cb947e536
--- /dev/null
+++ b/packages/web-util/src/components/ShowLocalNotification.tsx
@@ -0,0 +1,43 @@
+import { h, Fragment, VNode } from "preact";
+import { Attention } from "./Attention.js";
+import { Notification } from "../index.browser.js";
+// import { useSettings } from "../hooks/settings.js";
+
+export function ShowLocalNotification({ notification }: { notification?: Notification }): VNode {
+ if (!notification) return <Fragment />
+ switch (notification.message.type) {
+ case "error":
+ return <div class="relative">
+ <div class="fixed top-0 left-0 right-0 z-20 w-full p-4">
+ <Attention type="danger" title={notification.message.title} onClose={() => {
+ notification.remove()
+ }}>
+ {notification.message.description &&
+ <div class="mt-2 text-sm text-red-700">
+ {notification.message.description}
+ </div>
+ }
+ {/* <MaybeShowDebugInfo info={notification.message.debug} /> */}
+ </Attention>
+ </div>
+ </div>
+ case "info":
+ return <div class="relative">
+ <div class="fixed top-0 left-0 right-0 z-20 w-full p-4">
+ <Attention type="success" title={notification.message.title} onClose={() => {
+ notification.remove();
+ }} /></div></div>
+ }
+}
+
+
+// function MaybeShowDebugInfo({ info }: { info: any }): VNode {
+// const [settings] = useSettings()
+// if (settings.showDebugInfo) {
+// return <pre class="whitespace-break-spaces ">
+// {info}
+// </pre>
+// }
+// return <Fragment />
+// }
+
diff --git a/packages/web-util/src/components/index.ts b/packages/web-util/src/components/index.ts
index 9441e971d..8344d4a7a 100644
--- a/packages/web-util/src/components/index.ts
+++ b/packages/web-util/src/components/index.ts
@@ -1 +1,8 @@
export * as utils from "./utils.js";
+export * from "./Attention.js";
+export * from "./CopyButton.js";
+export * from "./ErrorLoading.js";
+export * from "./LangSelector.js";
+export * from "./Loading.js";
+export * from "./ShowInputErrorLabel.js";
+export * from "./ShowLocalNotification.js";