From 5eff8914f97f94f5f0dfcd9de1838883c799497b Mon Sep 17 00:00:00 2001 From: Jubal Mabaquiao Date: Fri, 1 Nov 2024 14:07:36 +0800 Subject: [PATCH 1/4] repaced bigaddress component with multilinecard --- app/css/interceptor.css | 124 ++++++++++++++++++ .../SimulationSummary.tsx | 1 + .../SimpleSendVisualisations.tsx | 2 +- .../subcomponents/MultilineCard.tsx | 93 +++++++++++++ app/ts/components/subcomponents/address.tsx | 86 ++++-------- 5 files changed, 245 insertions(+), 61 deletions(-) create mode 100644 app/ts/components/subcomponents/MultilineCard.tsx diff --git a/app/css/interceptor.css b/app/css/interceptor.css index 27bd563b..93ad3d0d 100644 --- a/app/css/interceptor.css +++ b/app/css/interceptor.css @@ -1736,6 +1736,130 @@ header:has(form[role='search']) h1 { } } +.multiline-card { + --bg-color: #484848; + --button-color: #77738ccc; + --image-size: 2.25rem; + --min-text-width: 3ch; + --pad-x: 0; + --pad-y: 0; + --gap-x: 0.5rem; + ---space: 0.425rem; + --edge-roundness: 3px; + --status-backdrop-color: #303030ed; + --status-success-color: #4fb64f; + + 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; + 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)); + + data { + line-height: 1em; + color: var(--text-color); + text-align: left; + min-width: var(--min-text-width); + } + + button { + font: inherit; + background: var(--bg-color); + border: 0 none; + padding: 0; + cursor: pointer; + + &:hover, &:focus { background: var(--bg-color) } + } + + > [role=img] { + grid-area: top / left / bottom / data; + align-self: center; + font-size: var(--image-size); + line-height: 1; + + & svg, img { + display: inline-block; + vertical-align: -0.15em; + } + } + + > :has(data ~ button) { + display: inline-grid; + align-items: baseline; + grid-template-columns: [left] minmax(0, min-content) [right]; + grid-template-rows: [top] min-content [bottom]; + + /* title */ + &:nth-of-type(2) { + grid-area: top / data / sub / right; + data { + font-weight: 600; + } + } + + /* subtitle */ + &:nth-of-type(3) { + grid-area: sub / data / bottom / right; + data { + font-size: 0.825em; + color: var(--disabled-text-color); + } + } + + > data { grid-area: top / left / bottom / right } + + > button { + grid-area: top / left / bottom / right; + display: inline-grid; + align-items: baseline; + grid-template-columns: minmax(0, 1fr) min-content; + background-color: var(--bg-color); + opacity: 0; + outline: none; + + &:is(.multiline-card:hover *, .multiline-card:focus-within *) { opacity: 1 } + + &:hover, &:focus { + > span { + background: white; + color: black; + + span { display: inline } + svg { display: none } + } + } + + > span { + background: var(--button-color); + color: white; + font-size: 0.8em; + font-weight: 600; + padding-inline: 0.25em; + border-radius: 2px; + text-transform: uppercase; + line-height: 1.4; + + span { + display: none; + font-size: 0.8em; + } + + svg { + display: inline-block; + vertical-align: -0.125em; + } + } + } + } +} + .tooltip { background: #222222; color: #ffffff; diff --git a/app/ts/components/simulationExplaining/SimulationSummary.tsx b/app/ts/components/simulationExplaining/SimulationSummary.tsx index 4e1a92e8..26fda912 100644 --- a/app/ts/components/simulationExplaining/SimulationSummary.tsx +++ b/app/ts/components/simulationExplaining/SimulationSummary.tsx @@ -351,6 +351,7 @@ function SummarizeAddress(param: SummarizeAddressParams) { : - + { beforeAndAfter === undefined ? <> : diff --git a/app/ts/components/subcomponents/MultilineCard.tsx b/app/ts/components/subcomponents/MultilineCard.tsx new file mode 100644 index 00000000..ca78b88f --- /dev/null +++ b/app/ts/components/subcomponents/MultilineCard.tsx @@ -0,0 +1,93 @@ +import { JSX } from 'preact/jsx-runtime' +import { useSignal } from '@preact/signals' +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 + label: ActionableTextProps + note: ActionableTextProps + style?: JSX.CSSProperties +} + +export const MultilineCard = ({ icon, label, note, style }: MultilineCardProps) => { + 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 CardIcon = icon.component + const defaultAction:TextAction = { label: 'Copy', icon: () => , onClick: copyTextToClipboard } + + return ( + <> +
+ + + + + +
+ + + ) +} + +export type TextAction = { + label: string + icon: () => JSX.Element + onClick?: JSX.MouseEventHandler +} + +export type ActionableTextProps = { + displayText: string + value?: string + action: TextAction | 'noaction' | undefined +} + +type TextNodeProps = { + displayText: string, + value: string +} + +const TextNode = ({ displayText, value }: TextNodeProps) => { displayText } + +const ActionableText = ({ displayText, value, action }: ActionableTextProps) => { + const DisplayText = () => + return ( + + + { action !== undefined && action !== 'noaction' ? : <> } + + ) +} + +type TextActionProps = { + icon: () => JSX.Element + textNode: () => JSX.Element + label: string + onClick?: JSX.MouseEventHandler +} + +const TextAction = ({ textNode: DisplayText, icon: ActionIcon, label, onClick }: TextActionProps) => { + return ( + + ) +} + diff --git a/app/ts/components/subcomponents/address.tsx b/app/ts/components/subcomponents/address.tsx index 66ee9430..d357e51c 100644 --- a/app/ts/components/subcomponents/address.tsx +++ b/app/ts/components/subcomponents/address.tsx @@ -4,9 +4,10 @@ import { checksummedAddress } from '../../utils/bigint.js' import { RenameAddressCallBack } from '../../types/user-interface-types.js' import { AddressBookEntries, AddressBookEntry } from '../../types/addressBookTypes.js' import { Website } from '../../types/websiteAccessTypes.js' -import { CopyToClipboard } from './CopyToClipboard.js' import { Blockie } from './SVGBlockie.js' import { InlineCard } from './InlineCard.js' +import { EditIcon } from './icons.js' +import { ActionableTextProps, MultilineCard, TextAction } from './MultilineCard.js' export function getActiveAddressEntry(addressToFind: bigint, activeAddresses: AddressBookEntries): AddressBookEntry { for (const info of activeAddresses) { @@ -53,73 +54,38 @@ type BigAddressParams = { readonly noCopying?: boolean readonly noEditAddress?: boolean readonly renameAddressCallBack: RenameAddressCallBack + readonly style?: JSX.CSSProperties } 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 = title !== addrString ? addrString : '' - - return
-
- { !params.noCopying && addrString !== undefined ? - - - - : - - } -
+ const subTitle = addrString && title !== addrString ? addrString : '(Not in addressbook)' -
- - - { !params.noCopying && addrString !== undefined ? - - - - : - } - - - - { !params.noCopying && addrString !== undefined && subTitle !== undefined ? - - - - : - - } -
-
-} + const renameAddressAction:TextAction = { + label: 'Edit', + icon: () => , + onClick: () => params.addressBookEntry && params.renameAddressCallBack(params.addressBookEntry) + } -const AddressTitle = ({ content, useLegibleFont }: { content: string, useLegibleFont?: boolean }) => { - return

{ content }

-} + const labelConfig:ActionableTextProps = { + displayText: title, + action: !params.noEditAddress && title !== addrString ? renameAddressAction : undefined + } + + const noteConfig:ActionableTextProps = { + displayText: subTitle, + action: addrString && subTitle !== addrString ? renameAddressAction : undefined + } -const AddressSubTitle = ({ content }: { content?: string }) => { - if (!content) return <> - return

{ content }

+ return ( + params.addressBookEntry ? : <> } } + style = { params.style } + /> + ) } type ActiveAddressParams = { From 6a9a03402c73a175a9a0e31ffaf1814f1bb9bd63 Mon Sep 17 00:00:00 2001 From: Jubal Mabaquiao Date: Thu, 7 Nov 2024 15:46:13 +0800 Subject: [PATCH 2/4] remove unnecessary css declaration --- app/css/interceptor.css | 3 --- 1 file changed, 3 deletions(-) diff --git a/app/css/interceptor.css b/app/css/interceptor.css index 93ad3d0d..578f99a5 100644 --- a/app/css/interceptor.css +++ b/app/css/interceptor.css @@ -1744,10 +1744,7 @@ header:has(form[role='search']) h1 { --pad-x: 0; --pad-y: 0; --gap-x: 0.5rem; - ---space: 0.425rem; --edge-roundness: 3px; - --status-backdrop-color: #303030ed; - --status-success-color: #4fb64f; font: inherit; display: inline-grid; From 4edcc7e94a1f071b5ab5c73f33ae015382689f72 Mon Sep 17 00:00:00 2001 From: Jubal Mabaquiao Date: Fri, 8 Nov 2024 11:57:42 +0800 Subject: [PATCH 3/4] Apply suggestions from code review Co-authored-by: KillariDev <13102010+KillariDev@users.noreply.github.com> --- app/ts/components/subcomponents/address.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/ts/components/subcomponents/address.tsx b/app/ts/components/subcomponents/address.tsx index d357e51c..ce34fad3 100644 --- a/app/ts/components/subcomponents/address.tsx +++ b/app/ts/components/subcomponents/address.tsx @@ -68,12 +68,12 @@ export function BigAddress(params: BigAddressParams) { onClick: () => params.addressBookEntry && params.renameAddressCallBack(params.addressBookEntry) } - const labelConfig:ActionableTextProps = { + const labelConfig: ActionableTextProps = { displayText: title, action: !params.noEditAddress && title !== addrString ? renameAddressAction : undefined } - const noteConfig:ActionableTextProps = { + const noteConfig: ActionableTextProps = { displayText: subTitle, action: addrString && subTitle !== addrString ? renameAddressAction : undefined } From 1b319cc9772b73a12e75dc8c1007f46349e44f10 Mon Sep 17 00:00:00 2001 From: KillariDev <13102010+KillariDev@users.noreply.github.com> Date: Thu, 21 Nov 2024 11:36:00 +0200 Subject: [PATCH 4/4] Update app/ts/components/subcomponents/address.tsx --- app/ts/components/subcomponents/address.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/ts/components/subcomponents/address.tsx b/app/ts/components/subcomponents/address.tsx index ce34fad3..e686c4fc 100644 --- a/app/ts/components/subcomponents/address.tsx +++ b/app/ts/components/subcomponents/address.tsx @@ -62,7 +62,7 @@ export function BigAddress(params: BigAddressParams) { const title = params.addressBookEntry === undefined ? 'No address found' : params.addressBookEntry.name const subTitle = addrString && title !== addrString ? addrString : '(Not in addressbook)' - const renameAddressAction:TextAction = { + const renameAddressAction: TextAction = { label: 'Edit', icon: () => , onClick: () => params.addressBookEntry && params.renameAddressCallBack(params.addressBookEntry)