Skip to content

Commit

Permalink
remove default multiline card action
Browse files Browse the repository at this point in the history
  • Loading branch information
jubalm committed Nov 12, 2024
1 parent 4edcc7e commit e7dbdfd
Show file tree
Hide file tree
Showing 3 changed files with 131 additions and 59 deletions.
20 changes: 15 additions & 5 deletions app/css/interceptor.css
Original file line number Diff line number Diff line change
Expand Up @@ -1737,21 +1737,22 @@ 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;
display: inline-grid;
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);
Expand All @@ -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) }
}

Expand Down Expand Up @@ -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;
Expand All @@ -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 {
Expand Down
120 changes: 80 additions & 40 deletions app/ts/components/subcomponents/MultilineCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
<figure class = 'multiline-card' role = 'figure' style = { style }>
<ActionableIcon { ...icon } />
<ActionableText { ...label } />
<ActionableText { ...note } />
</figure>
)
}

type ActionableIconAction = {
onClick: JSX.MouseEventHandler<HTMLButtonElement>
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<TooltipConfig | undefined>(undefined)

const copyTextToClipboard = async (event: JSX.TargetedMouseEvent<HTMLButtonElement>) => {
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: () => <CopyIcon />, 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 (
<>
<figure class = 'multiline-card' role = 'figure' style = { style }>
<span role = 'img'>
<button type = 'button' onClick = { icon.onClick || copyTextToClipboard } tabIndex = { -1 } title = { icon.tooltipText || label.displayText }><CardIcon /></button>
</span>
<ActionableText { ...label } action = { !label.action ? defaultAction : label.action } />
<ActionableText { ...note } action = { !note.action ? defaultAction : note.action } />
</figure>
<Tooltip config = { tooltipConfig } />
</>
<span role = 'img'>
<button type = 'button' onClick = { handleClick } tabIndex = { -1 } value = { copyValue } title = { hintText } disabled = { !props.action }>
<CardIcon />
<Tooltip config = { tooltipConfig } />
</button>
</span>
)
}

export type TextAction = {
label: string
icon: () => JSX.Element
onClick?: JSX.MouseEventHandler<HTMLButtonElement>
type TextNodeProps = {
displayText: string,
value: string
}

const TextNode = ({ displayText, value }: TextNodeProps) => <data class = 'truncate text-legible' value = { value || displayText }>{ displayText }</data>

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<HTMLButtonElement>
}

const TextNode = ({ displayText, value }: TextNodeProps) => <data class = 'truncate text-legible' value = { value || displayText }>{ displayText }</data>
const ActionableText = (props: ActionableTextProps) => {
const tooltipConfig = useSignal<TooltipConfig | undefined>(undefined)

const copyTextToClipboard = async (event: JSX.TargetedMouseEvent<HTMLButtonElement>) => {
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 = () => <TextNode displayText = { props.displayText } value = { copyValue } />

const actionIcon = props.action ? props.action === 'clipboard-copy' ? () => <CopyIcon /> : 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 = () => <TextNode displayText = { displayText } value = { value || displayText } />
return (
<span>
<DisplayText />
{ action !== undefined && action !== 'noaction' ? <TextAction { ...action } textNode = { DisplayText } /> : <></> }
<TextAction label = { actionButtonLabel } textNode = { DisplayText } icon = { actionIcon } onClick = { actionHandler } copyValue = { copyValue } />
<Tooltip config = { tooltipConfig } />
</span>
)
}

type TextActionProps = {
icon: () => JSX.Element
type TextActionProps = ActionableTextAction & {
textNode: () => JSX.Element
label: string
onClick?: JSX.MouseEventHandler<HTMLButtonElement>
copyValue: string
}

const TextAction = ({ textNode: DisplayText, icon: ActionIcon, label, onClick }: TextActionProps) => {
const TextAction = (props: TextActionProps) => {
const DisplayText = props.textNode
const ActionIcon = props.icon

return (
<button type = 'button' onClick = { onClick } value = { '' } tabIndex = { 1 }>
<button type = 'button' onClick = { props.onClick } value = { props.copyValue } tabIndex = { 1 } disabled = { !props.onClick }>
<DisplayText />
<span>
<ActionIcon />
<span>{ label }</span>
<span>{ props.label }</span>
</span>
</button>
)
Expand Down
50 changes: 36 additions & 14 deletions app/ts/components/subcomponents/address.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -40,7 +40,7 @@ export function AddressIcon(param: AddressIconParams) {
if (param.logoUri !== undefined) {
return (
<AddressIconFrame isBig = { param.isBig }>
<img src = { param.logoUri } style = { { display: 'block', width: '1em', minWidth: '1em', height: '1em' } }/>
<img src = { param.logoUri } style = { { display: 'block', width: '1em', minWidth: '1em', height: '1em' } } />
</AddressIconFrame>
)
}
Expand All @@ -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: () => <EditIcon />,
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 ? <Blockie address = { params.addressBookEntry.address } /> : <></>,
...(!params.noCopying && addressString) ? {
action: 'clipboard-copy',
copyValue: addressString
} : {
action: undefined
}
}

return (
<MultilineCard
label = { labelConfig }
note = { noteConfig }
icon = { { component: () => params.addressBookEntry ? <Blockie address = { params.addressBookEntry.address } /> : <></> } }
icon = { iconConfig }
style = { params.style }
/>
)
Expand Down Expand Up @@ -130,13 +152,13 @@ export function SmallAddress({ addressBookEntry, renameAddressCallBack, style }:
return <></>
}

return <InlineCard label={ addressBookEntry.name } copyValue = { addressString } icon={ generateIcon } onEditClicked={ () => renameAddressCallBack(addressBookEntry) } style = { style } />
return <InlineCard label = { addressBookEntry.name } copyValue = { addressString } icon = { generateIcon } onEditClicked = { () => renameAddressCallBack(addressBookEntry) } style = { style } />
}

export function WebsiteOriginText( { icon, websiteOrigin, title }: Website) {
export function WebsiteOriginText({ icon, websiteOrigin, title }: Website) {
return <div class = 'card-header-icon unsetcursor' style = 'width: 100%; padding: 0'>
<span style = 'width: 24px; height: 24px; min-width: 24px'>
{ icon === undefined ? <></> : <img src = { icon } style = 'width: 24px; height: 24px;'/> }
{ icon === undefined ? <></> : <img src = { icon } style = 'width: 24px; height: 24px;' /> }
</span>

<div class = 'media-content' style = 'overflow-y: hidden; overflow-x: clip; display: block; padding-left: 10px;'>
Expand Down

0 comments on commit e7dbdfd

Please sign in to comment.