diff --git a/app/css/interceptor.css b/app/css/interceptor.css index f2df2378..88289d4f 100644 --- a/app/css/interceptor.css +++ b/app/css/interceptor.css @@ -1721,13 +1721,14 @@ header:has(form[role='search']) h1 { } .multiline-card { - --bg-color: #484848; + --bg-color: transparent; --button-color: #77738ccc; --image-size: 2.25rem; - --min-text-width: 3ch; + --min-text-width: 0; --pad-x: 0; --pad-y: 0; --gap-x: 0.5rem; + --gap-y: 0.0625rem; --edge-roundness: 3px; font: inherit; @@ -1735,12 +1736,12 @@ header:has(form[role='search']) h1 { grid-template-columns: [left] minmax(0, min-content) [data] minmax(0, max-content) [right]; grid-template-rows: [top] min-content [sub] min-content [bottom]; column-gap: var(--gap-x); - row-gap: 2px; + row-gap: var(--gap-y); padding-block: var(--pad-y); padding-inline: var(--pad-x); background-color: var(--bg-color); border-radius: var(--edge-roundness); - min-width: calc(var(--min-text-width) + var(--image-size) + (var(-pad-x) * 2) + var(--gap-x)); + min-width: calc(var(--min-text-width) + var(--image-size) + (var(--pad-x) * 2) + var(--gap-x)); data { line-height: 1em; @@ -1754,8 +1755,8 @@ header:has(form[role='search']) h1 { background: var(--bg-color); border: 0 none; padding: 0; - cursor: pointer; + &:not(:disabled) { cursor: pointer } &:hover, &:focus { background: var(--bg-color) } } @@ -1794,7 +1795,13 @@ header:has(form[role='search']) h1 { } } - > data { grid-area: top / left / bottom / right } + > data { + grid-area: top / left / bottom / right; + + &:not(:has(+:disabled)) { + .multiline-card:hover &, .multiline-card:focus-within & { visibility: hidden } + } + } > button { grid-area: top / left / bottom / right; @@ -1802,10 +1809,13 @@ header:has(form[role='search']) h1 { align-items: baseline; grid-template-columns: minmax(0, 1fr) min-content; background-color: var(--bg-color); - opacity: 0; outline: none; + position: relative; + visibility: hidden; - &:is(.multiline-card:hover *, .multiline-card:focus-within *) { opacity: 1 } + &:not(:disabled) { + .multiline-card:hover &, .multiline-card:focus-within & { visibility: visible } + } &:hover, &:focus { > span { diff --git a/app/ts/components/subcomponents/MultilineCard.tsx b/app/ts/components/subcomponents/MultilineCard.tsx index ca78b88f..2eff04ab 100644 --- a/app/ts/components/subcomponents/MultilineCard.tsx +++ b/app/ts/components/subcomponents/MultilineCard.tsx @@ -4,88 +4,135 @@ import { Tooltip, TooltipConfig } from './Tooltip.js' import { clipboardCopy } from './clipboardcopy.js' import { CopyIcon } from './icons.js' -export type CardIcon = { - component: () => JSX.Element - onClick?: () => void - tooltipText?: string -} - export type MultilineCardProps = { - icon: CardIcon + icon: ActionableIconProps label: ActionableTextProps note: ActionableTextProps style?: JSX.CSSProperties } export const MultilineCard = ({ icon, label, note, style }: MultilineCardProps) => { + return ( +
+ + + +
+ ) +} + +export type ActionableIconProps = { + onClick: 'clipboard-copy' + icon: () => JSX.Element + copyValue?: string + copySuccessMessage: string + hintText?: string +} | { + onClick: JSX.MouseEventHandler + icon: () => JSX.Element + hintText?: string +} | { + onClick: undefined + icon: () => JSX.Element +} + +const ActionableIcon = (props: ActionableIconProps) => { const tooltipConfig = useSignal(undefined) const copyTextToClipboard = async (event: JSX.TargetedMouseEvent) => { event.currentTarget.blur() await clipboardCopy(event.currentTarget.value) - tooltipConfig.value = { message: 'Copied!', x: event.clientX, y: event.clientY } + const copySuccessMessage = props.onClick === 'clipboard-copy' && props.copySuccessMessage ? props.copySuccessMessage : 'Copied!' + tooltipConfig.value = { message: copySuccessMessage, x: event.clientX, y: event.clientY } } - const CardIcon = icon.component - const defaultAction:TextAction = { label: 'Copy', icon: () => , onClick: copyTextToClipboard } + const CardIcon = props.icon + const handleClick = props.onClick ? props.onClick === 'clipboard-copy' ? copyTextToClipboard : props.onClick : undefined + const copyValue = props.onClick === 'clipboard-copy' ? props.copyValue : undefined + const hintText = props.onClick ? props.hintText : undefined return ( - <> -
- - - - - -
- - + + + ) } -export type TextAction = { - label: string - icon: () => JSX.Element - onClick?: JSX.MouseEventHandler +type TextNodeProps = { + displayText: string, + value: string } +const TextNode = ({ displayText, value }: TextNodeProps) => { displayText } + export type ActionableTextProps = { + onClick: 'clipboard-copy' + displayText: string + copyValue?: string + copySuccessMessage?: string +} | { + onClick: JSX.MouseEventHandler + displayText: string + buttonLabel: string + buttonIcon: () => JSX.Element +} | { + onClick?: undefined displayText: string - value?: string - action: TextAction | 'noaction' | undefined } -type TextNodeProps = { - displayText: string, - value: string -} +const ActionableText = (props: ActionableTextProps) => { + const tooltipConfig = useSignal(undefined) -const TextNode = ({ displayText, value }: TextNodeProps) => { displayText } + const copyTextToClipboard = async (event: JSX.TargetedMouseEvent) => { + event.currentTarget.blur() + await clipboardCopy(event.currentTarget.value) + tooltipConfig.value = { + message: props.onClick === 'clipboard-copy' && props.copySuccessMessage ? props.copySuccessMessage : 'Copied!', + x: event.clientX, + y: event.clientY + } + } + + const copyValue = props.onClick === 'clipboard-copy' && props.copyValue ? props.copyValue : props.displayText + const actionIcon = props.onClick ? props.onClick === 'clipboard-copy' ? () => : props.buttonIcon : () => <> + const actionHandler = props.onClick ? props.onClick === 'clipboard-copy' ? copyTextToClipboard : props.onClick : undefined + const actionButtonLabel = props.onClick ? props.onClick === 'clipboard-copy' ? 'Copy' : props.buttonLabel : '' + + const DisplayText = () => -const ActionableText = ({ displayText, value, action }: ActionableTextProps) => { - const DisplayText = () => return ( - { action !== undefined && action !== 'noaction' ? : <> } + + ) } type TextActionProps = { - icon: () => JSX.Element + onClick: undefined textNode: () => JSX.Element - label: string - onClick?: JSX.MouseEventHandler +} | { + onClick: JSX.MouseEventHandler + textNode: () => JSX.Element + buttonLabel: string + buttonIcon: () => JSX.Element + copyValue?: string } -const TextAction = ({ textNode: DisplayText, icon: ActionIcon, label, onClick }: TextActionProps) => { +const TextAction = (props: TextActionProps) => { + const DisplayText = props.textNode + const ActionIcon = props.onClick ? props.buttonIcon : () => <> + return ( - ) diff --git a/app/ts/components/subcomponents/address.tsx b/app/ts/components/subcomponents/address.tsx index e686c4fc..2d71fb00 100644 --- a/app/ts/components/subcomponents/address.tsx +++ b/app/ts/components/subcomponents/address.tsx @@ -7,7 +7,7 @@ import { Website } from '../../types/websiteAccessTypes.js' import { Blockie } from './SVGBlockie.js' import { InlineCard } from './InlineCard.js' import { EditIcon } from './icons.js' -import { ActionableTextProps, MultilineCard, TextAction } from './MultilineCard.js' +import { ActionableIconProps, ActionableTextProps, MultilineCard } from './MultilineCard.js' export function getActiveAddressEntry(addressToFind: bigint, activeAddresses: AddressBookEntries): AddressBookEntry { for (const info of activeAddresses) { @@ -40,7 +40,7 @@ export function AddressIcon(param: AddressIconParams) { if (param.logoUri !== undefined) { return ( - + ) } @@ -58,34 +58,38 @@ type BigAddressParams = { } export function BigAddress(params: BigAddressParams) { - const addrString = params.addressBookEntry && checksummedAddress(params.addressBookEntry.address) - const title = params.addressBookEntry === undefined ? 'No address found' : params.addressBookEntry.name - const subTitle = addrString && title !== addrString ? addrString : '(Not in addressbook)' - - const renameAddressAction: TextAction = { - label: 'Edit', - icon: () => , - onClick: () => params.addressBookEntry && params.renameAddressCallBack(params.addressBookEntry) + const addressString = params.addressBookEntry && checksummedAddress(params.addressBookEntry.address) + const labelText = params.addressBookEntry?.name || addressString || 'No address found' + const noteText = addressString && addressString !== labelText ? addressString : '(Not in addressbook)' + + const configPartialWithEditOnClick = { + onClick: () => params.addressBookEntry && params.renameAddressCallBack(params.addressBookEntry), + buttonLabel: 'Edit', + buttonIcon: () => + } + + const configPartialWithCopyOnClick = { + onClick: 'clipboard-copy' as const, + copyValue: addressString, + copySuccessMessage: 'Address copied!' } const labelConfig: ActionableTextProps = { - displayText: title, - action: !params.noEditAddress && title !== addrString ? renameAddressAction : undefined + displayText: labelText, + ...(labelText === addressString && !params.noCopying) ? configPartialWithCopyOnClick : configPartialWithEditOnClick } const noteConfig: ActionableTextProps = { - displayText: subTitle, - action: addrString && subTitle !== addrString ? renameAddressAction : undefined + displayText: noteText, + ...(noteText === addressString && !params.noCopying) ? configPartialWithCopyOnClick : configPartialWithEditOnClick + } + + const iconConfig: ActionableIconProps = { + icon: () => params.addressBookEntry ? : <>, + ...(!params.noCopying && addressString) ? configPartialWithCopyOnClick : { onClick: undefined } } - return ( - params.addressBookEntry ? : <> } } - style = { params.style } - /> - ) + return } type ActiveAddressParams = { @@ -130,13 +134,13 @@ export function SmallAddress({ addressBookEntry, renameAddressCallBack, style }: return <> } - return renameAddressCallBack(addressBookEntry) } style = { style } /> + return renameAddressCallBack(addressBookEntry) } style = { style } /> } -export function WebsiteOriginText( { icon, websiteOrigin, title }: Website) { +export function WebsiteOriginText({ icon, websiteOrigin, title }: Website) { return
- { icon === undefined ? <> : } + { icon === undefined ? <> : }