diff --git a/app/css/interceptor.css b/app/css/interceptor.css index 578f99a5..6d5af223 100644 --- a/app/css/interceptor.css +++ b/app/css/interceptor.css @@ -1737,13 +1737,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; --pad-x: 0; --pad-y: 0; --gap-x: 0.5rem; + --gap-y: 0.0625rem; --edge-roundness: 3px; font: inherit; @@ -1751,7 +1752,7 @@ 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); @@ -1770,8 +1771,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) } } @@ -1810,7 +1811,13 @@ header:has(form[role='search']) h1 { } } - > data { grid-area: top / left / bottom / right } + > data { + grid-area: top / left / bottom / right; + + .multiline-card:hover &:not(:has(+:disabled)), .multiline-card:focus-within &:not(:has(+:disabled)) { + opacity: 0; + } + } > button { grid-area: top / left / bottom / right; @@ -1820,8 +1827,11 @@ header:has(form[role='search']) h1 { background-color: var(--bg-color); opacity: 0; outline: none; + position: relative; - &:is(.multiline-card:hover *, .multiline-card:focus-within *) { opacity: 1 } + .multiline-card:hover &, .multiline-card:focus-within & { + &:not(:disabled) { opacity: 1 } + } &:hover, &:focus { > span { diff --git a/app/ts/components/subcomponents/MultilineCard.tsx b/app/ts/components/subcomponents/MultilineCard.tsx index ca78b88f..5de0b664 100644 --- a/app/ts/components/subcomponents/MultilineCard.tsx +++ b/app/ts/components/subcomponents/MultilineCard.tsx @@ -4,88 +4,128 @@ 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 ( +
+ + + +
+ ) +} + +type ActionableIconAction = { + onClick: JSX.MouseEventHandler + hintText?: string +} + +export type ActionableIconProps = { + icon: () => JSX.Element + hintText?: string + action: 'clipboard-copy' + copyValue: string + copySuccessMessage?: string +} | { + icon: () => JSX.Element + hintText?: string + action?: ActionableIconAction +} + +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.action === '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.action ? props.action === 'clipboard-copy' ? copyTextToClipboard : props.action.onClick : undefined + const copyValue = props.action === 'clipboard-copy' ? props.copyValue : undefined + const hintText = props.action !== 'clipboard-copy' ? props.action?.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 = { displayText: string - value?: string - action: TextAction | 'noaction' | undefined + action: 'clipboard-copy' + copyValue?: string + copySuccessMessage?: string +} | { + displayText: string + action?: ActionableTextAction } -type TextNodeProps = { - displayText: string, - value: string +type ActionableTextAction = { + label: string + icon: () => JSX.Element + onClick?: JSX.MouseEventHandler } -const TextNode = ({ displayText, value }: TextNodeProps) => { displayText } +const ActionableText = (props: ActionableTextProps) => { + 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 copyValue = props.action === 'clipboard-copy' && props.copyValue ? props.copyValue : props.displayText + const DisplayText = () => + + const actionIcon = props.action ? props.action === 'clipboard-copy' ? () => : props.action.icon : () => <> + const actionHandler = props.action ? props.action === 'clipboard-copy' ? copyTextToClipboard : props.action.onClick : undefined + const actionButtonLabel = props.action === 'clipboard-copy' ? 'Copy' : props.action?.label || '' -const ActionableText = ({ displayText, value, action }: ActionableTextProps) => { - const DisplayText = () => return ( - { action !== undefined && action !== 'noaction' ? : <> } + + ) } -type TextActionProps = { - icon: () => JSX.Element +type TextActionProps = ActionableTextAction & { textNode: () => JSX.Element - label: string - onClick?: JSX.MouseEventHandler + copyValue: string } -const TextAction = ({ textNode: DisplayText, icon: ActionIcon, label, onClick }: TextActionProps) => { +const TextAction = (props: TextActionProps) => { + const DisplayText = props.textNode + const ActionIcon = props.icon + return ( - ) diff --git a/app/ts/components/subcomponents/address.tsx b/app/ts/components/subcomponents/address.tsx index ce34fad3..742cd6cc 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,31 +58,53 @@ 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 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 renameAddressAction:TextAction = { + const renameAddressAction: ActionableTextProps['action'] = { label: 'Edit', icon: () => , onClick: () => params.addressBookEntry && params.renameAddressCallBack(params.addressBookEntry) } const labelConfig: ActionableTextProps = { - displayText: title, - action: !params.noEditAddress && title !== addrString ? renameAddressAction : undefined + displayText: labelText, + ...(labelText === addressString && params.noCopying === undefined) ? { + action: 'clipboard-copy', + copyValue: addressString, + copySuccessMessage: 'Address copied!' + } : { + action: params.noEditAddress === undefined ? renameAddressAction : undefined + } } const noteConfig: ActionableTextProps = { - displayText: subTitle, - action: addrString && subTitle !== addrString ? renameAddressAction : undefined + displayText: noteText, + ...(noteText === addressString && !params.noCopying) ? { + action: 'clipboard-copy', + copyValue: addressString, + copySuccessMessage: 'Address copied!' + } : { + action: params.noEditAddress === undefined ? renameAddressAction : undefined + } + } + + const iconConfig: ActionableIconProps = { + icon: () => params.addressBookEntry ? : <>, + ...(!params.noCopying && addressString) ? { + action: 'clipboard-copy', + copyValue: addressString + } : { + action: undefined + } } return ( params.addressBookEntry ? : <> } } + icon = { iconConfig } style = { params.style } /> ) @@ -130,13 +152,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 ? <> : }