chore: cleanup AsyncIcon element

This commit is contained in:
2023-10-16 16:54:55 +01:00
parent 497ef7bf9a
commit dec2b9ce2e
10 changed files with 66 additions and 78 deletions

View File

@ -1,31 +1,15 @@
import "./AsyncButton.css";
import React, { useState, ForwardedRef } from "react";
import React, { ForwardedRef } from "react";
import Spinner from "../Icons/Spinner";
import useLoading from "Hooks/useLoading";
import classNames from "classnames";
interface AsyncButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
disabled?: boolean;
onClick(e: React.MouseEvent): Promise<void> | void;
children?: React.ReactNode;
export interface AsyncButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
onClick?: (e: React.MouseEvent) => Promise<void> | void;
}
const AsyncButton = React.forwardRef<HTMLButtonElement, AsyncButtonProps>((props, ref) => {
const [loading, setLoading] = useState<boolean>(false);
async function handle(e: React.MouseEvent) {
e.stopPropagation();
if (loading || props.disabled) return;
setLoading(true);
try {
if (typeof props.onClick === "function") {
const f = props.onClick(e);
if (f instanceof Promise) {
await f;
}
}
} finally {
setLoading(false);
}
}
const { handle, loading } = useLoading(props.onClick, props.disabled);
return (
<button
@ -33,7 +17,7 @@ const AsyncButton = React.forwardRef<HTMLButtonElement, AsyncButtonProps>((props
type="button"
disabled={loading || props.disabled}
{...props}
className={`spinner-button${props.className ? ` ${props.className}` : ""}`}
className={classNames("spinner-button", props.className)}
onClick={handle}>
<span style={{ visibility: loading ? "hidden" : "visible" }}>{props.children}</span>
{loading && (

View File

@ -1,35 +1,23 @@
import Icon from "Icons/Icon";
import useLoading from "Hooks/useLoading";
import Spinner from "Icons/Spinner";
import { HTMLProps, useState } from "react";
import classNames from "classnames";
export interface AsyncIconProps extends HTMLProps<HTMLDivElement> {
export type AsyncIconProps = React.HTMLProps<HTMLDivElement> & {
iconName: string;
iconSize?: number;
loading?: boolean;
onClick?: (e: React.MouseEvent<HTMLDivElement>) => Promise<void>;
}
onClick?: (e: React.MouseEvent) => Promise<void> | void;
};
export function AsyncIcon(props: AsyncIconProps) {
const [loading, setLoading] = useState(props.loading ?? false);
async function handleClick(e: React.MouseEvent<HTMLDivElement>) {
setLoading(true);
try {
if (props.onClick) {
await props.onClick(e);
}
} catch (ex) {
console.error(ex);
}
setLoading(false);
}
const { loading, handle } = useLoading(props.onClick, props.disabled);
const mergedProps = { ...props } as Record<string, unknown>;
delete mergedProps["iconName"];
delete mergedProps["iconSize"];
delete mergedProps["loading"];
return (
<div {...mergedProps} onClick={e => handleClick(e)}>
<div {...mergedProps} onClick={handle} className={classNames("button-icon-sm", props.className)}>
{loading ? <Spinner /> : <Icon name={props.iconName} size={props.iconSize} />}
{props.children}
</div>

View File

@ -211,7 +211,7 @@ export function NoteCreator() {
});
}
async function onSubmit(ev: React.MouseEvent<HTMLButtonElement>) {
async function onSubmit(ev: React.MouseEvent) {
ev.stopPropagation();
await sendNote();
}
@ -450,14 +450,20 @@ export function NoteCreator() {
showFollowingMark={false}
/>
{note.pollOptions === undefined && !note.replyTo && (
<div className="note-creator-icon">
<Icon name="pie-chart" onClick={() => note.update(v => (v.pollOptions = ["A", "B"]))} size={24} />
</div>
<AsyncIcon
iconName="pie-chart"
iconSize={24}
onClick={() => note.update(v => (v.pollOptions = ["A", "B"]))}
className="note-creator-icon"
/>
)}
<AsyncIcon iconName="image-plus" iconSize={24} onClick={attachFile} className="note-creator-icon" />
<button className="secondary" onClick={() => note.update(v => (v.advanced = !v.advanced))}>
<FormattedMessage defaultMessage="Advanced" />
</button>
<AsyncIcon
iconName="settings-04"
iconSize={24}
onClick={() => note.update(v => (v.advanced = !v.advanced))}
className="note-creator-icon"
/>
</div>
<div className="flex g8">
<button className="secondary" onClick={cancel}>

View File

@ -1,9 +1,10 @@
import React, { HTMLProps, useEffect, useState } from "react";
import React, { useEffect, useState } from "react";
import { FormattedMessage, useIntl } from "react-intl";
import { useLongPress } from "use-long-press";
import { TaggedNostrEvent, ParsedZap, countLeadingZeros, NostrLink } from "@snort/system";
import { useUserProfile } from "@snort/system-react";
import { Menu, MenuItem } from "@szhsin/react-menu";
import classNames from "classnames";
import { formatShort } from "Number";
import useEventPublisher from "Hooks/useEventPublisher";
@ -11,7 +12,7 @@ import { delay, findTag, normalizeReaction } from "SnortUtils";
import { NoteCreator } from "Element/Event/NoteCreator";
import SendSats from "Element/SendSats";
import { ZapsSummary } from "Element/Event/Zap";
import { AsyncIcon } from "Element/AsyncIcon";
import { AsyncIcon, AsyncIconProps } from "Element/AsyncIcon";
import { useWallet } from "Wallet";
import useLogin from "Hooks/useLogin";
@ -308,18 +309,11 @@ export default function NoteFooter(props: NoteFooterProps) {
);
}
interface AsyncFooterIconProps extends HTMLProps<HTMLDivElement> {
iconName: string;
value: number;
loading?: boolean;
onClick?: (e: React.MouseEvent<HTMLDivElement>) => Promise<void>;
}
function AsyncFooterIcon(props: AsyncFooterIconProps) {
function AsyncFooterIcon(props: AsyncIconProps & { value: number }) {
const mergedProps = {
...props,
iconSize: 18,
className: `transition duration-200 ease-in-out reaction-pill${props.className ? ` ${props.className}` : ""}`,
className: classNames("transition duration-200 ease-in-out reaction-pill", props.className),
};
return (
<AsyncIcon {...mergedProps}>

View File

@ -0,0 +1,20 @@
import { useState } from "react";
export default function useLoading<T>(fn: ((e: React.MouseEvent) => Promise<T> | T) | undefined, disabled?: boolean) {
const [loading, setLoading] = useState<boolean>(false);
async function handle(e: React.MouseEvent) {
e.stopPropagation();
if (loading || disabled) return;
setLoading(true);
try {
if (typeof fn === "function") {
await fn(e);
}
} finally {
setLoading(false);
}
}
return { handle, loading };
}

View File

@ -24,6 +24,7 @@ import { getDisplayName } from "Element/User/DisplayName";
import { Day } from "Const";
import Tabs, { Tab } from "Element/Tabs";
import classNames from "classnames";
import { AsyncIcon } from "Element/AsyncIcon";
function notificationContext(ev: TaggedNostrEvent) {
switch (ev.kind) {
@ -194,15 +195,15 @@ function NotificationSummary({ evs }: { evs: Array<TaggedNostrEvent> }) {
);
}, [evs, period]);
const filterIcon = (f: NotificationSummaryFilter, icon: string, iconActiveClass?: string) => {
const filterIcon = (f: NotificationSummaryFilter, icon: string, iconActiveClass: string) => {
const active = hasFlag(filter, f);
return (
<button
type="button"
className={classNames("icon-sm transparent", { active: active })}
onClick={() => setFilter(v => v ^ f)}>
<Icon name={icon} className={active ? iconActiveClass : undefined} />
</button>
<AsyncIcon
className={classNames("button-icon-sm transparent", { active, [iconActiveClass]: active })}
onClick={() => setFilter(v => v ^ f)}
name={""}
iconName={icon}
/>
);
};

View File

@ -337,20 +337,15 @@ button.icon:hover {
color: var(--highlight);
}
button.icon-sm {
.button-icon-sm {
padding: 4px;
border-radius: 8px;
display: flex;
align-items: center;
cursor: pointer;
color: var(--gray-light) !important;
}
button.icon-sm:not(.active):hover {
background-color: var(--gray-dark);
}
button.icon-sm.active {
.button-icon-sm.active {
background: rgba(255, 255, 255, 0.1);
}

View File

@ -120,9 +120,6 @@
"3KNMbJ": {
"defaultMessage": "Articles"
},
"3Rx6Qo": {
"defaultMessage": "Advanced"
},
"3cc4Ct": {
"defaultMessage": "Light"
},

View File

@ -39,7 +39,6 @@
"2k0Cv+": "Dislikes ({n})",
"2ukA4d": "{n} hours",
"3KNMbJ": "Articles",
"3Rx6Qo": "Advanced",
"3cc4Ct": "Light",
"3gOsZq": "Translators",
"3qnJlS": "You are voting with {amount} sats",