feat: classnames
This commit is contained in:
parent
7129ffa1c7
commit
2ce5bd153b
@ -18,6 +18,7 @@
|
||||
"@types/use-sync-external-store": "^0.0.4",
|
||||
"@uidotdev/usehooks": "^2.3.1",
|
||||
"@void-cat/api": "^1.0.10",
|
||||
"classnames": "^2.3.2",
|
||||
"debug": "^4.3.4",
|
||||
"dexie": "^3.2.4",
|
||||
"emojilib": "^3.0.10",
|
||||
|
@ -1,4 +1,5 @@
|
||||
import { useState, ReactNode } from "react";
|
||||
import classNames from "classnames";
|
||||
|
||||
import Icon from "Icons/Icon";
|
||||
import ShowMore from "Element/Event/ShowMore";
|
||||
@ -38,15 +39,13 @@ interface CollapsedSectionProps {
|
||||
export const CollapsedSection = ({ title, children, className }: CollapsedSectionProps) => {
|
||||
const [collapsed, setCollapsed] = useState(true);
|
||||
const icon = (
|
||||
<div className={`collapse-icon ${collapsed ? "" : "flip"}`}>
|
||||
<div className={classNames("collapse-icon", { flip: !collapsed })}>
|
||||
<Icon name="arrowFront" />
|
||||
</div>
|
||||
);
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
className={`collapsable-section${className ? ` ${className}` : ""}`}
|
||||
onClick={() => setCollapsed(!collapsed)}>
|
||||
<div className={classNames("collapsable-section", className)} onClick={() => setCollapsed(!collapsed)}>
|
||||
{title}
|
||||
<CollapsedIcon icon={icon} collapsed={collapsed} />
|
||||
</div>
|
||||
|
@ -1,4 +1,5 @@
|
||||
import "./Copy.css";
|
||||
import classNames from "classnames";
|
||||
import Icon from "Icons/Icon";
|
||||
import { useCopy } from "useCopy";
|
||||
|
||||
@ -13,7 +14,7 @@ export default function Copy({ text, maxSize = 32, className }: CopyProps) {
|
||||
const trimmed = text.length > maxSize ? `${text.slice(0, sliceLength)}...${text.slice(-sliceLength)}` : text;
|
||||
|
||||
return (
|
||||
<div className={`copy flex pointer g8${className ? ` ${className}` : ""}`} onClick={() => copy(text)}>
|
||||
<div className={classNames("copy flex pointer g8", className)} onClick={() => copy(text)}>
|
||||
<span className="copy-body">{trimmed}</span>
|
||||
<span className="icon" style={{ color: copied ? "var(--success)" : "var(--highlight)" }}>
|
||||
{copied ? <Icon name="check" size={14} /> : <Icon name="copy-solid" size={14} />}
|
||||
|
@ -3,6 +3,7 @@ import { useState } from "react";
|
||||
import { useIntl, FormattedMessage } from "react-intl";
|
||||
import { useMemo } from "react";
|
||||
import { decodeInvoice } from "@snort/shared";
|
||||
import classNames from "classnames";
|
||||
|
||||
import SendSats from "Element/SendSats";
|
||||
import Icon from "Icons/Icon";
|
||||
@ -60,7 +61,7 @@ export default function Invoice(props: InvoiceProps) {
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className={`note-invoice flex ${isExpired ? "expired" : ""} ${isPaid ? "paid" : ""}`}>
|
||||
<div className={classNames("note-invoice flex", { expired: isExpired, paid: isPaid })}>
|
||||
<div className="invoice-header">{header()}</div>
|
||||
|
||||
<p className="invoice-amount">
|
||||
|
@ -8,6 +8,7 @@ import useLogin from "Hooks/useLogin";
|
||||
import Icon from "Icons/Icon";
|
||||
import { useNoteCreator } from "State/NoteCreator";
|
||||
import { NoteCreator } from "./NoteCreator";
|
||||
import classNames from "classnames";
|
||||
|
||||
export const NoteCreatorButton = ({ className }: { className?: string }) => {
|
||||
const buttonRef = useRef<HTMLButtonElement>(null);
|
||||
@ -36,7 +37,7 @@ export const NoteCreatorButton = ({ className }: { className?: string }) => {
|
||||
<>
|
||||
<button
|
||||
ref={buttonRef}
|
||||
className={`primary circle${className ? ` ${className}` : ""}`}
|
||||
className={classNames("primary circle", className)}
|
||||
onClick={() =>
|
||||
update(v => {
|
||||
v.replyTo = undefined;
|
||||
|
@ -2,7 +2,9 @@ import { Link, useNavigate } from "react-router-dom";
|
||||
import React, { ReactNode, useMemo, useState } from "react";
|
||||
import { useInView } from "react-intersection-observer";
|
||||
import { FormattedMessage, useIntl } from "react-intl";
|
||||
import classNames from "classnames";
|
||||
import { EventExt, EventKind, HexKey, Lists, NostrLink, NostrPrefix, TaggedNostrEvent } from "@snort/system";
|
||||
|
||||
import { findTag, hexToBech32, profileLink } from "SnortUtils";
|
||||
import useModeration from "Hooks/useModeration";
|
||||
import useLogin from "Hooks/useLogin";
|
||||
@ -27,7 +29,7 @@ import { chainKey } from "Hooks/useThreadContext";
|
||||
export function NoteInner(props: NoteProps) {
|
||||
const { data: ev, related, highlight, options: opt, ignoreModeration = false, className } = props;
|
||||
|
||||
const baseClassName = `note card${className ? ` ${className}` : ""}`;
|
||||
const baseClassName = classNames("note card", className);
|
||||
const navigate = useNavigate();
|
||||
const [showReactions, setShowReactions] = useState(false);
|
||||
|
||||
@ -328,7 +330,7 @@ export function NoteInner(props: NoteProps) {
|
||||
}
|
||||
|
||||
const note = (
|
||||
<div className={`${baseClassName}${highlight ? " active " : " "}`} onClick={e => goToEvent(e, ev)} ref={ref}>
|
||||
<div className={classNames(baseClassName, { active: highlight })} onClick={e => goToEvent(e, ev)} ref={ref}>
|
||||
{content()}
|
||||
</div>
|
||||
);
|
||||
|
@ -3,6 +3,7 @@ import { useMemo, useState, ReactNode, useContext } from "react";
|
||||
import { useIntl } from "react-intl";
|
||||
import { useNavigate, useParams } from "react-router-dom";
|
||||
import { TaggedNostrEvent, u256, NostrPrefix, EventExt, parseNostrLink, NostrLink } from "@snort/system";
|
||||
import classNames from "classnames";
|
||||
|
||||
import { getAllLinkReactions, getLinkReactions } from "SnortUtils";
|
||||
import BackButton from "Element/BackButton";
|
||||
@ -83,14 +84,17 @@ const ThreadNote = ({ active, note, isLast, isLastSubthread, related, chains, on
|
||||
const [collapsed, setCollapsed] = useState(!activeInReplies);
|
||||
const hasMultipleNotes = replies.length > 1;
|
||||
const isLastVisibleNote = isLastSubthread && isLast && !hasMultipleNotes;
|
||||
const className = `subthread-container ${isLast && collapsed ? "subthread-last" : "subthread-multi subthread-mid"}`;
|
||||
const className = classNames(
|
||||
"subthread-container",
|
||||
isLast && collapsed ? "subthread-last" : "subthread-multi subthread-mid",
|
||||
);
|
||||
return (
|
||||
<>
|
||||
<div className={className}>
|
||||
<Divider variant="small" />
|
||||
<Note
|
||||
highlight={active === note.id}
|
||||
className={`thread-note ${isLastVisibleNote ? "is-last-note" : ""}`}
|
||||
className={classNames("thread-note", { "is-last-note": isLastVisibleNote })}
|
||||
data={note}
|
||||
key={note.id}
|
||||
related={related}
|
||||
@ -156,13 +160,15 @@ const TierThree = ({ active, isLastSubthread, notes, related, chains, onNavigate
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
className={`subthread-container ${hasMultipleNotes ? "subthread-multi" : ""} ${
|
||||
isLast ? "subthread-last" : "subthread-mid"
|
||||
}`}>
|
||||
className={classNames("subthread-container", {
|
||||
"subthread-multi": hasMultipleNotes,
|
||||
"subthread-last": isLast,
|
||||
"subthread-mid": !isLast,
|
||||
})}>
|
||||
<Divider variant="small" />
|
||||
<Note
|
||||
highlight={active === first.id}
|
||||
className={`thread-note ${isLastSubthread && isLast ? "is-last-note" : ""}`}
|
||||
className={classNames("thread-note", { "is-last-note": isLastSubthread && isLast })}
|
||||
data={first}
|
||||
key={first.id}
|
||||
related={related}
|
||||
@ -188,12 +194,14 @@ const TierThree = ({ active, isLastSubthread, notes, related, chains, onNavigate
|
||||
return (
|
||||
<div
|
||||
key={r.id}
|
||||
className={`subthread-container ${lastReply ? "" : "subthread-multi"} ${
|
||||
lastReply ? "subthread-last" : "subthread-mid"
|
||||
}`}>
|
||||
className={classNames("subthread-container", {
|
||||
"subthread-multi": !lastReply,
|
||||
"subthread-last": !lastReply,
|
||||
"subthread-mid": lastReply,
|
||||
})}>
|
||||
<Divider variant="small" />
|
||||
<Note
|
||||
className={`thread-note ${lastNote ? "is-last-note" : ""}`}
|
||||
className={classNames("thread-note", { "is-last-note": lastNote })}
|
||||
highlight={active === r.id}
|
||||
data={r}
|
||||
key={r.id}
|
||||
|
@ -1,14 +1,19 @@
|
||||
import classNames from "classnames";
|
||||
import Icon, { IconProps } from "Icons/Icon";
|
||||
import type { ReactNode } from "react";
|
||||
|
||||
interface IconButtonProps {
|
||||
onClick(): void;
|
||||
children: ReactNode;
|
||||
onClick?: () => void;
|
||||
icon: IconProps;
|
||||
className?: string;
|
||||
children?: ReactNode;
|
||||
}
|
||||
|
||||
const IconButton = ({ onClick, children }: IconButtonProps) => {
|
||||
const IconButton = ({ onClick, icon, children, className }: IconButtonProps) => {
|
||||
return (
|
||||
<button className="icon" type="button" onClick={onClick}>
|
||||
<div className="icon-wrapper">{children}</div>
|
||||
<button className={classNames("icon", className)} type="button" onClick={onClick}>
|
||||
<Icon {...icon} />
|
||||
{children}
|
||||
</button>
|
||||
);
|
||||
};
|
||||
|
@ -1,6 +1,7 @@
|
||||
import "./Text.css";
|
||||
import { ReactNode, useState } from "react";
|
||||
import { HexKey, ParsedFragment } from "@snort/system";
|
||||
import classNames from "classnames";
|
||||
|
||||
import Invoice from "Element/Embed/Invoice";
|
||||
import Hashtag from "Element/Embed/Hashtag";
|
||||
@ -276,7 +277,7 @@ export default function Text({
|
||||
};
|
||||
|
||||
return (
|
||||
<div dir="auto" className={`text${className ? ` ${className}` : ""}`} onClick={onClick}>
|
||||
<div dir="auto" className={classNames("text", className)} onClick={onClick}>
|
||||
{renderContent()}
|
||||
{showSpotlight && <SpotlightMediaModal images={images} onClose={() => setShowSpotlight(false)} idx={imageIdx} />}
|
||||
</div>
|
||||
|
@ -2,6 +2,7 @@ import "./Avatar.css";
|
||||
|
||||
import { CSSProperties, ReactNode, useEffect, useState } from "react";
|
||||
import type { UserMetadata } from "@snort/system";
|
||||
import classNames from "classnames";
|
||||
|
||||
import useImgProxy from "Hooks/useImgProxy";
|
||||
import { getDisplayName } from "Element/User/DisplayName";
|
||||
@ -44,7 +45,7 @@ const Avatar = ({ pubkey, user, size, onClick, image, imageOverlay, icons, class
|
||||
<div
|
||||
onClick={onClick}
|
||||
style={style}
|
||||
className={`avatar${imageOverlay ? " with-overlay" : ""} ${className ?? ""}`}
|
||||
className={classNames("avatar", { "with-overlay": imageOverlay }, className)}
|
||||
data-domain={domain?.toLowerCase()}
|
||||
title={getDisplayName(user, "")}>
|
||||
{icons && <div className="icons">{icons}</div>}
|
||||
|
@ -2,6 +2,7 @@ import "./NoteToSelf.css";
|
||||
import { Link, useNavigate } from "react-router-dom";
|
||||
import FormattedMessage from "Element/FormattedMessage";
|
||||
import { profileLink } from "SnortUtils";
|
||||
import classNames from "classnames";
|
||||
|
||||
import messages from "../messages";
|
||||
import Icon from "Icons/Icon";
|
||||
@ -31,9 +32,9 @@ export default function NoteToSelf({ pubkey, clickable, className, link }: NoteT
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={`nts${className ? ` ${className}` : ""}`}>
|
||||
<div className={classNames("nts", className)}>
|
||||
<div className="avatar-wrapper">
|
||||
<div className={`avatar${clickable ? " clickable" : ""}`}>
|
||||
<div className={classNames("avatar", { clickable: clickable })}>
|
||||
<Icon onClick={clickLink} name="book-closed" size={20} />
|
||||
</div>
|
||||
</div>
|
||||
|
@ -6,6 +6,7 @@ import { HexKey, UserMetadata } from "@snort/system";
|
||||
import { useUserProfile } from "@snort/system-react";
|
||||
import { useHover } from "@uidotdev/usehooks";
|
||||
import { ControlledMenu } from "@szhsin/react-menu";
|
||||
import classNames from "classnames";
|
||||
|
||||
import { profileLink } from "SnortUtils";
|
||||
import Avatar from "Element/User/Avatar";
|
||||
@ -157,7 +158,7 @@ export default function ProfileImage({
|
||||
if (link === "") {
|
||||
return (
|
||||
<>
|
||||
<div className={`pfp${className ? ` ${className}` : ""}`} onClick={handleClick}>
|
||||
<div className={classNames("pfp", className)} onClick={handleClick}>
|
||||
{inner()}
|
||||
</div>
|
||||
{profileCard()}
|
||||
@ -167,7 +168,7 @@ export default function ProfileImage({
|
||||
return (
|
||||
<>
|
||||
<Link
|
||||
className={`pfp${className ? ` ${className}` : ""}`}
|
||||
className={classNames("pfp", className)}
|
||||
to={link === undefined ? profileLink(pubkey) : link}
|
||||
onClick={handleClick}>
|
||||
{inner()}
|
||||
|
@ -1,8 +1,6 @@
|
||||
import IconProps from "./IconProps";
|
||||
|
||||
export const BlueWallet = (props: IconProps) => {
|
||||
export const BlueWallet = (props: { width?: number; height?: number }) => {
|
||||
return (
|
||||
<svg width="58px" height="58px" viewBox="0 0 58 58" version="1.1" xmlns="http://www.w3.org/2000/svg" {...props}>
|
||||
<svg viewBox="0 0 58 58" version="1.1" xmlns="http://www.w3.org/2000/svg" {...props}>
|
||||
<title>logo-bluewallet</title>
|
||||
<defs>
|
||||
<filter x="-14.0%" y="-13.8%" width="128.1%" height="127.6%" filterUnits="objectBoundingBox" id="filter-1">
|
||||
|
@ -1,15 +1,15 @@
|
||||
import { MouseEventHandler } from "react";
|
||||
import IconsSvg from "public/icons.svg";
|
||||
|
||||
type Props = {
|
||||
export interface IconProps {
|
||||
name: string;
|
||||
size?: number;
|
||||
height?: number;
|
||||
className?: string;
|
||||
onClick?: MouseEventHandler<SVGSVGElement>;
|
||||
};
|
||||
}
|
||||
|
||||
const Icon = (props: Props) => {
|
||||
const Icon = (props: IconProps) => {
|
||||
const size = props.size || 20;
|
||||
const href = `${IconsSvg}#` + props.name;
|
||||
|
||||
|
@ -1,5 +0,0 @@
|
||||
export default interface IconProps {
|
||||
className?: string;
|
||||
width?: number;
|
||||
height?: number;
|
||||
}
|
@ -1,6 +1,4 @@
|
||||
import IconProps from "./IconProps";
|
||||
|
||||
export default function NostrIcon(props: IconProps) {
|
||||
export default function NostrIcon(props: { width?: number; height?: number }) {
|
||||
return (
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 116.446 84.924" {...props}>
|
||||
<path
|
||||
|
@ -1,7 +1,6 @@
|
||||
import IconProps from "./IconProps";
|
||||
import "./Spinner.css";
|
||||
|
||||
const Spinner = (props: IconProps) => (
|
||||
const Spinner = (props: { width?: number; height?: number; className?: string }) => (
|
||||
<svg
|
||||
width={props.width ?? 20}
|
||||
height={props.height ?? 20}
|
||||
|
@ -53,23 +53,6 @@
|
||||
max-height: 300px; /* Cap images in notifications to 300px height */
|
||||
}
|
||||
|
||||
.summary-icon {
|
||||
padding: 4px;
|
||||
border-radius: 8px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
color: var(--gray-light) !important;
|
||||
}
|
||||
|
||||
.summary-icon:not(.active):hover {
|
||||
background-color: var(--gray-dark);
|
||||
}
|
||||
|
||||
.summary-icon.active {
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
.summary-tooltip {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
|
@ -23,6 +23,7 @@ import ProfilePreview from "Element/User/ProfilePreview";
|
||||
import { getDisplayName } from "Element/User/DisplayName";
|
||||
import { Day } from "Const";
|
||||
import Tabs, { Tab } from "Element/Tabs";
|
||||
import classNames from "classnames";
|
||||
|
||||
function notificationContext(ev: TaggedNostrEvent) {
|
||||
switch (ev.kind) {
|
||||
@ -196,9 +197,12 @@ function NotificationSummary({ evs }: { evs: Array<TaggedNostrEvent> }) {
|
||||
const filterIcon = (f: NotificationSummaryFilter, icon: string, iconActiveClass?: string) => {
|
||||
const active = hasFlag(filter, f);
|
||||
return (
|
||||
<div className={`summary-icon${active ? " active" : ""}`} onClick={() => setFilter(v => v ^ f)}>
|
||||
<button
|
||||
type="button"
|
||||
className={classNames("icon-sm transparent", { active: active })}
|
||||
onClick={() => setFilter(v => v ^ f)}>
|
||||
<Icon name={icon} className={active ? iconActiveClass : undefined} />
|
||||
</div>
|
||||
</button>
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -305,10 +305,8 @@ export default function ProfilePage({ id: propId }: ProfilePageProps) {
|
||||
|
||||
const link = encodeTLV(NostrPrefix.Profile, id);
|
||||
return (
|
||||
<div className="icon-actions">
|
||||
<IconButton onClick={() => setShowProfileQr(true)}>
|
||||
<Icon name="qr" size={16} />
|
||||
</IconButton>
|
||||
<>
|
||||
<IconButton onClick={() => setShowProfileQr(true)} icon={{ name: "qr", size: 16 }} />
|
||||
{showProfileQr && (
|
||||
<Modal id="profile-qr" className="qr-modal" onClose={() => setShowProfileQr(false)}>
|
||||
<ProfileImage pubkey={id} />
|
||||
@ -324,11 +322,7 @@ export default function ProfilePage({ id: propId }: ProfilePageProps) {
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
{lnurl && (
|
||||
<IconButton onClick={() => setShowLnQr(true)}>
|
||||
<Icon name="zap" size={16} />
|
||||
</IconButton>
|
||||
)}
|
||||
{lnurl && <IconButton onClick={() => setShowLnQr(true)} icon={{ name: "zap", size: 16 }} />}
|
||||
{loginPubKey && !login.readonly && (
|
||||
<>
|
||||
<IconButton
|
||||
@ -340,14 +334,14 @@ export default function ProfilePage({ id: propId }: ProfilePageProps) {
|
||||
value: id,
|
||||
})}`,
|
||||
)
|
||||
}>
|
||||
<Icon name="envelope" size={16} />
|
||||
</IconButton>
|
||||
}
|
||||
icon={{ name: "envelope", size: 16 }}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -324,6 +324,23 @@ button.icon:hover {
|
||||
color: var(--highlight);
|
||||
}
|
||||
|
||||
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 {
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
.btn {
|
||||
padding: 10px;
|
||||
border-radius: 5px;
|
||||
|
@ -2720,6 +2720,7 @@ __metadata:
|
||||
"@webpack-cli/generators": ^3.0.4
|
||||
"@webscopeio/react-textarea-autocomplete": ^4.9.2
|
||||
babel-loader: ^9.1.3
|
||||
classnames: ^2.3.2
|
||||
config: ^3.3.9
|
||||
copy-webpack-plugin: ^11.0.0
|
||||
css-loader: ^6.7.3
|
||||
@ -5114,7 +5115,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"classnames@npm:^2.2.5":
|
||||
"classnames@npm:^2.2.5, classnames@npm:^2.3.2":
|
||||
version: 2.3.2
|
||||
resolution: "classnames@npm:2.3.2"
|
||||
checksum: 2c62199789618d95545c872787137262e741f9db13328e216b093eea91c85ef2bfb152c1f9e63027204e2559a006a92eb74147d46c800a9f96297ae1d9f96f4e
|
||||
|
Loading…
x
Reference in New Issue
Block a user