From 03246c729b81d7b0ecfeffce2e3ccbdc7e270363 Mon Sep 17 00:00:00 2001 From: storywithoutend Date: Tue, 2 Apr 2024 16:55:30 +0800 Subject: [PATCH 1/4] initial version --- public/locales/en/profile.json | 2 +- .../@atoms/EllipsisName/EllipsisName.tsx | 127 ++++++++++++++++ src/components/@atoms/Name/Name.tsx | 143 ++++++++++++++++++ .../Name/utils/calculateInlineName.test.ts | 51 +++++++ .../@atoms/Name/utils/calculateInlineName.ts | 31 ++++ .../Name/utils/calculateWrapName.test.ts | 89 +++++++++++ .../@atoms/Name/utils/calculateWrapName.ts | 85 +++++++++++ .../AvatarWithIdentifier.tsx | 17 ++- .../RolesSection/components/RoleRow.tsx | 17 ++- .../PermissionsTab/OwnershipPermissions.tsx | 59 ++++++-- .../tabs/PermissionsTab/PermissionsTab.tsx | 25 ++- .../[name]/tabs/PermissionsTab/Section.tsx | 34 ++--- src/hooks/dom/useElementDimensions.ts | 60 ++++++++ src/pages/index.tsx | 29 ++++ .../views/MainView/components/RoleCard.tsx | 19 ++- 15 files changed, 747 insertions(+), 41 deletions(-) create mode 100644 src/components/@atoms/EllipsisName/EllipsisName.tsx create mode 100644 src/components/@atoms/Name/Name.tsx create mode 100644 src/components/@atoms/Name/utils/calculateInlineName.test.ts create mode 100644 src/components/@atoms/Name/utils/calculateInlineName.ts create mode 100644 src/components/@atoms/Name/utils/calculateWrapName.test.ts create mode 100644 src/components/@atoms/Name/utils/calculateWrapName.ts create mode 100644 src/hooks/dom/useElementDimensions.ts diff --git a/public/locales/en/profile.json b/public/locales/en/profile.json index 07e26da80..130871ec6 100644 --- a/public/locales/en/profile.json +++ b/public/locales/en/profile.json @@ -109,7 +109,7 @@ }, "permissions": { "name": "Permissions", - "parentUnlockedWarning": "You cannot change permissions on this name. You must first revoke ‘unwrap this name’ on the parent ({{parent}}). Click here to view the parent’s permissions.", + "parentUnlockedWarning": "You cannot change permissions on this name. You must first revoke ‘unwrap this name’ on the parent (<0>{{parent}}). Click here to view the parent’s permissions.", "revokedLabel": "Revoked {{date}}", "grantedLabel": "Granted {{date}}", "role": { diff --git a/src/components/@atoms/EllipsisName/EllipsisName.tsx b/src/components/@atoms/EllipsisName/EllipsisName.tsx new file mode 100644 index 000000000..a15ad0d6f --- /dev/null +++ b/src/components/@atoms/EllipsisName/EllipsisName.tsx @@ -0,0 +1,127 @@ +import { useEffect, useRef, useState } from 'react' +import styled, { css } from 'styled-components' + +import { StyledLink } from '../StyledLink' + +const Container = styled.span( + () => css` + word-break: keep-all; + overflow-wrap: normal; + /* display: flex; */ + overflow: hidden; + justify-content: left; + `, +) + +const HiddenSpan = styled.span( + () => css` + position: absolute; + pointer-events: none; + visibility: hidden; + white-space: nowrap; + `, +) + +const VisibleSpan = styled.span( + () => css` + white-space: nowrap; + `, +) + +type Props = { + children: string + padding?: number + containerRef?: React.RefObject +} + +const calculateSlice = ({ + node, + ellipsisWidth, + maxWidth, +}: { + node: HTMLSpanElement | null + ellipsisWidth: number + maxWidth?: number +}): number | null => { + if (!node) return null + + const parentElementWidth = maxWidth || node.parentElement?.offsetWidth || Infinity + const nodeWidth = node.offsetWidth || Infinity + if (nodeWidth <= parentElementWidth) return null + + const children = node?.children || [] + let slice = 0 + let total = ellipsisWidth + for (let index = 0; index < Math.floor((children.length - 1) / 2); index += 1) { + const element = children[index] as HTMLSpanElement + const matchElement = children[children.length - 1 - index] as HTMLSpanElement + total += element.offsetWidth + matchElement.offsetWidth + if (total > parentElementWidth) return slice + slice += 1 + } + + return null +} + +export const EllipsisName = ({ children, padding = 0, containerRef }: Props) => { + const charArray = children.split('') + + const [name, setName] = useState(children) + + const ref = useRef(null) + const ellipsisRef = useRef(null) + + const guide = (node: HTMLSpanElement) => { + const maxWidth = (containerRef?.current?.offsetWidth || Infinity) - padding + const ellipsisWidth = ellipsisRef.current?.offsetWidth || 0 + const slice = calculateSlice({ node, ellipsisWidth, maxWidth }) + const _name = + slice !== null + ? `${children.slice(0, slice)}\u2026${children.slice(children.length - slice!)}` + : name + setName(_name) + } + + useEffect(() => { + const updateName = () => { + if (ref.current) { + guide(ref.current) + } + } + updateName() + window.addEventListener('resize', updateName) + return () => { + window.removeEventListener('resize', updateName) + } + }, []) + + const dots = '\u2026' + + return ( + + {dots} + + {charArray.map((char, i) => ( + // eslint-disable-next-line react/no-array-index-key + {char} + ))} + + {name} + + ) +} + +export const TransComponentEllipsisName = ({ + children, + href, + ...props +}: { + href?: string + containerRef?: React.RefObject + padding?: number + children?: string[] +}) => { + const name = children?.[0] || '' + const baseComponent = {name} + return href ? {baseComponent} : baseComponent +} diff --git a/src/components/@atoms/Name/Name.tsx b/src/components/@atoms/Name/Name.tsx new file mode 100644 index 000000000..f803dcc62 --- /dev/null +++ b/src/components/@atoms/Name/Name.tsx @@ -0,0 +1,143 @@ +import { useEffect, useRef, useState } from 'react' +import styled, { css } from 'styled-components' +import { match } from 'ts-pattern' + +import { StyledLink } from '../StyledLink' +import { calculateInlineName } from './utils/calculateInlineName' +import { calculateWrapName } from './utils/calculateWrapName' + +const Container = styled.span( + () => css` + word-break: keep-all; + overflow-wrap: normal; + /* display: flex; */ + overflow: hidden; + justify-content: left; + position: relative; + `, +) + +const HiddenSpan = styled.span( + () => css` + position: absolute; + pointer-events: none; + visibility: hidden; + white-space: nowrap; + `, +) + +const VisibleSpan = styled.span<{ type: 'wrap' | 'inline' }>( + ({ type }) => css` + ${type === 'inline' && 'white-space: nowrap;'} + `, +) + +type BaseProps = { + children: string + type: 'wrap' | 'inline' + wrapLines?: number + containerWidth?: number + containerLeft?: number +} + +type InlineProps = { + type: 'inline' + containerWidth: number + wrapLines?: never + containerLeft?: never +} + +type WrapProps = { + type: 'wrap' + wrapLines: number + containerWidth: number + containerLeft: number +} + +type Props = BaseProps & (InlineProps | WrapProps) + +export const Name = ({ + type = 'inline', + wrapLines, + children, + containerWidth, + containerLeft, +}: Props) => { + const charArray = children.split('') + + const [name, setName] = useState(children) + + const ref = useRef(null) + const hiddenRef = useRef(null) + const ellipsisRef = useRef(null) + + const updateLayout = (node: HTMLSpanElement | null) => { + if (!node) return + const _name = match(type) + .with('wrap', () => { + console.log('wrapper offsets', ref.current?.offsetWidth, ref.current?.offsetLeft) + const nodeRect = node.getBoundingClientRect() + const initialWidth = containerWidth + containerLeft! - nodeRect.left + const ellipsisWidth = ellipsisRef.current?.offsetWidth || 0 + return calculateWrapName({ + name: children, + node, + ellipsisWidth, + maxWidth: containerWidth, + initialWidth, + lines: wrapLines, + }) + }) + .otherwise(() => { + console.log('containerWidth', containerWidth) + const ellipsisWidth = ellipsisRef.current?.offsetWidth || 0 + return calculateInlineName({ + name: children, + node, + ellipsisWidth, + maxWidth: containerWidth, + }) + }) + console.log('name', _name) + setName(_name) + } + + useEffect(() => { + updateLayout(hiddenRef.current) + }, [containerWidth, containerLeft]) + + useEffect(() => { + const updateLayoutCallback = () => updateLayout(hiddenRef?.current) + window.addEventListener('resize', updateLayoutCallback) + return () => { + window.removeEventListener('resize', updateLayoutCallback) + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []) + + return ( + + + + {charArray.map((char, i) => ( + // eslint-disable-next-line react/no-array-index-key + {char} + ))} + + {name} + + ) +} + +export const TransComponentName = ({ + children, + href, + ...props +}: { + href?: string + children?: string[] +} & Omit) => { + const name = children?.[0] || '' + const baseComponent = {name} + return href ? {baseComponent} : baseComponent +} diff --git a/src/components/@atoms/Name/utils/calculateInlineName.test.ts b/src/components/@atoms/Name/utils/calculateInlineName.test.ts new file mode 100644 index 000000000..e782806e3 --- /dev/null +++ b/src/components/@atoms/Name/utils/calculateInlineName.test.ts @@ -0,0 +1,51 @@ +import { describe, it, expect } from 'vitest'; +import { calculateInlineName } from './calculateInlineName'; +const jsdom = require('jsdom'); +const { JSDOM } = jsdom; + +const createNode = (str: string) => { + const chars = str.split(''); + const innerHtml = chars.map((char) => `${char}`).join('') + const dom = new JSDOM(`
${innerHtml}
`) + const root = dom.window.document.getElementById('root') + + Object.defineProperties(dom.window.HTMLDivElement.prototype, { + offsetWidth: { + get() { + return chars.length * 5 + } + }, + offsetLeft: { + get() { + return 10 + } + } + }) + + Object.defineProperties(dom.window.HTMLSpanElement.prototype, { + offsetWidth: { + get() { + return 5 + } + }, + offsetLeft: { + get() { + return 10 + } + } + }) + + return dom.window.document.getElementById('root') +} + +describe('calculateInlineName', () => { + it('should return null if the node is null', () => { + const result = calculateInlineName({ + name: 'helloworld!', + node: createNode('helloworld!'), + ellipsisWidth: 5, + maxWidth: 30, + }) + expect(result).toBe('he…​d!') + }) +}) \ No newline at end of file diff --git a/src/components/@atoms/Name/utils/calculateInlineName.ts b/src/components/@atoms/Name/utils/calculateInlineName.ts new file mode 100644 index 000000000..e9197ab98 --- /dev/null +++ b/src/components/@atoms/Name/utils/calculateInlineName.ts @@ -0,0 +1,31 @@ +export const calculateInlineName = ({ + name, + node, + ellipsisWidth, + maxWidth, +}: { + name: string + node: HTMLSpanElement | null + ellipsisWidth: number + maxWidth: number +}) => { + if (!node) return name + + const parentElementWidth = maxWidth || node.parentElement?.offsetWidth || Infinity + const nodeWidth = node.offsetWidth || Infinity + if (nodeWidth <= parentElementWidth) return name + + const children = node?.children || [] + let slice = 0 + let total = ellipsisWidth + for (let index = 0; index < Math.floor((children.length - 1) / 2); index += 1) { + const element = children[index] as HTMLSpanElement + const matchElement = children[children.length - 1 - index] as HTMLSpanElement + total += element.offsetWidth + matchElement.offsetWidth + if (total > parentElementWidth) + return `${name.slice(0, slice)}\u2026\u200B${name.slice(name.length - slice)}` + slice += 1 + } + + return name +} diff --git a/src/components/@atoms/Name/utils/calculateWrapName.test.ts b/src/components/@atoms/Name/utils/calculateWrapName.test.ts new file mode 100644 index 000000000..dc02bc24f --- /dev/null +++ b/src/components/@atoms/Name/utils/calculateWrapName.test.ts @@ -0,0 +1,89 @@ +import { describe, it, expect } from 'vitest' +import { calculateWrapName, findNumbersAddingUpToSum, sliceStringByNumbers } from './calculateWrapName' +const jsdom = require('jsdom') +const { JSDOM } = jsdom + +const createNode = (str: string) => { + const innerHtml = str.split('').map((char) => `${char}`).join('') + const dom = new JSDOM(`${innerHtml}`) + + Object.defineProperties(dom.window.HTMLSpanElement.prototype, { + offsetWidth: { + get() { + return 5 + } + }, + offsetLeft: { + get() { + return 10 + } + } + }) + + return dom.window.document.getElementById('root') +} + +describe('findNumbersAddingUpToSum', () => { + it('should return numbers that add up just below the sum', () => { + const result = findNumbersAddingUpToSum([1, 2, 3, 4, 5], 7) + expect(result).toEqual([1, 2, 3]) + }) + + it('should return numbers that add up exactly to the sum', () => { + const result = findNumbersAddingUpToSum([1, 2, 3, 4, 5], 10) + expect(result).toEqual([1, 2, 3, 4]) + }) + + it('should return all the numbers if they do not add up to the sum', () => { + const result = findNumbersAddingUpToSum([1, 2, 3, 4, 5, 6, 7, 8, 9, 10], 700) + expect(result).toEqual([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]) + }) + + it('should return an empty array if the sum is 0', () => { + const result = findNumbersAddingUpToSum([1, 2, 3, 4, 5], 0) + expect(result).toEqual([]) + }) +}) + +describe('sliceStringByNumbers', () => { + it('should fill in the array with characters as long as there is a number available', () => { + const result = sliceStringByNumbers([1, 2, 3, 4, 5], 'helloworld!') + expect(result).toEqual(['h', 'el', 'low', 'orld', '!']) + }) + + it('should stop splitting the array once it has run out of characters', () => { + const result = sliceStringByNumbers([1, 2, 3, 4, 5, 6, 7], 'helloworld!') + expect(result).toEqual(['h', 'el', 'low', 'orld', '!']) + }) + + it('should stop splitting the array once it has run out of numbers', () => { + const result = sliceStringByNumbers([1], 'helloworld!') + expect(result).toEqual(['h']) + }) +}) + +describe('calculateWrapName', () => { + it('should return the correct result', () => { + const result = calculateWrapName({ + name: 'helloworld!', + node: createNode('helloworld!'), + ellipsisWidth: 5, + initialWidth: 10, + maxWidth: 20, + lines: Infinity + }) + expect(result).toEqual('h…​ell…​owo…​rld…​!') + }) + + it('should return the correct result', () => { + const result = calculateWrapName({ + name: 'helloworld!', + node: createNode('helloworld!'), + ellipsisWidth: 5, + initialWidth: 10, + maxWidth: 20, + lines: 2 + }) + expect(result).toEqual('h…​rld!') + }) +}) diff --git a/src/components/@atoms/Name/utils/calculateWrapName.ts b/src/components/@atoms/Name/utils/calculateWrapName.ts new file mode 100644 index 000000000..534698d8f --- /dev/null +++ b/src/components/@atoms/Name/utils/calculateWrapName.ts @@ -0,0 +1,85 @@ +export const findNumbersAddingUpToSum = (numbers: number[], sum: number) => { + let index = 0 + let total = 0 + while (index < numbers.length) { + const num = numbers[index] + if (total + num > sum) break + total += num + index += 1 + } + return numbers.slice(0, index) +} + +export const sliceStringByNumbers = (numbers: number[], str: string): string[] => { + const result = [] + let startIdx = 0 + for (let i = 0; i < numbers.length; i += 1) { + const sliceLength = numbers[i] + const slice = str.slice(startIdx, startIdx + sliceLength) + if (slice.length > 0) result.push(slice) + startIdx += sliceLength + } + + return result +} + +export const calculateWrapName = ({ + name, + node, + ellipsisWidth, + maxWidth, + initialWidth: minWidth, + lines = Infinity, +}: { + name: string + node: HTMLSpanElement | null + ellipsisWidth: number + maxWidth?: number + initialWidth?: number + lines?: number +}): string => { + if (!node) return name + + const _maxWidth = maxWidth || node.parentElement?.offsetWidth || Infinity + const initialWidth = + minWidth || _maxWidth - (node.parentElement?.offsetLeft || 0) - node.offsetLeft + + let currentGroup: number[] = [] + let currentGroupTotal = 0 + let result: number[][] = [] + + const children = node?.children || [] + for (let index = 0; index < children.length; index += 1) { + const element = children[index] as HTMLSpanElement + const charWidth = element.offsetWidth + currentGroupTotal += charWidth + const breakpoint = result.length === 0 ? initialWidth : _maxWidth + if (currentGroupTotal + ellipsisWidth > breakpoint) { + result.push(currentGroup) + currentGroup = [charWidth] + currentGroupTotal = charWidth + } else { + currentGroup.push(charWidth) + } + } + if (currentGroup.length) result.push(currentGroup) + + console.log(result.length, lines) + if (result.length > lines) { + const left = result.slice(0, lines - 1) + const right = result + .slice(lines - 1) + .reverse() + .flat() + console.log('left', left, right) + const filteredRight = findNumbersAddingUpToSum(right, _maxWidth) + result = [...left, filteredRight] + } + + const slices = result.map((group) => group.length) + const [last, ...reversedFirstSegments] = slices.reverse() + const firstSegments = reversedFirstSegments.reverse() + const firstNames = sliceStringByNumbers(firstSegments, name) + const lastSegment = name.slice(-last) + return [...firstNames, lastSegment].join('\u2026\u200B') +} diff --git a/src/components/@molecules/AvatarWithIdentifier/AvatarWithIdentifier.tsx b/src/components/@molecules/AvatarWithIdentifier/AvatarWithIdentifier.tsx index a833e7ce1..37ccf260a 100644 --- a/src/components/@molecules/AvatarWithIdentifier/AvatarWithIdentifier.tsx +++ b/src/components/@molecules/AvatarWithIdentifier/AvatarWithIdentifier.tsx @@ -3,7 +3,9 @@ import { Address } from 'viem' import { Typography } from '@ensdomains/thorin' +import { Name } from '@app/components/@atoms/Name/Name' import { AvatarWithZorb } from '@app/components/AvatarWithZorb' +import { useElementDimensions } from '@app/hooks/dom/useElementDimensions' import { usePrimaryName } from '@app/hooks/ensjs/public/usePrimaryName' import { QuerySpace } from '@app/types' import { shortenAddress } from '@app/utils/utils' @@ -20,11 +22,14 @@ const Container = styled.div( const TextContainer = styled.div( () => css` + flex: 1; + overflow: hidden; display: flex; flex-direction: column; align-items: flex-start; justify-content: center; gap: 0; + background: yellow; `, ) @@ -41,6 +46,7 @@ type Props = { subtitle?: string size?: QuerySpace shortenAddressAsTitle?: boolean + maxWidth?: number } export const AvatarWithIdentifier = ({ @@ -49,6 +55,7 @@ export const AvatarWithIdentifier = ({ subtitle, size = '10', shortenAddressAsTitle = true, + maxWidth, }: Props) => { const primary = usePrimaryName({ address, @@ -62,17 +69,23 @@ export const AvatarWithIdentifier = ({ const isTitleFullAddress = !shortenAddressAsTitle && !_name + console.log('maxWidth >>>>>', maxWidth) return ( - {isTitleFullAddress ? ( + + + {_name || ''} + + + {/* {isTitleFullAddress ? ( {_title} ) : ( {_title} - )} + )} */} {_subtitle && ( {_subtitle} diff --git a/src/components/pages/profile/[name]/tabs/OwnershipTab/sections/RolesSection/components/RoleRow.tsx b/src/components/pages/profile/[name]/tabs/OwnershipTab/sections/RolesSection/components/RoleRow.tsx index b96775d79..368c3ea8f 100644 --- a/src/components/pages/profile/[name]/tabs/OwnershipTab/sections/RolesSection/components/RoleRow.tsx +++ b/src/components/pages/profile/[name]/tabs/OwnershipTab/sections/RolesSection/components/RoleRow.tsx @@ -1,4 +1,4 @@ -import { useMemo } from 'react' +import { useMemo, useRef } from 'react' import { useTranslation } from 'react-i18next' import { useCopyToClipboard } from 'react-use' import styled, { css } from 'styled-components' @@ -16,6 +16,7 @@ import { import { AvatarWithIdentifier } from '@app/components/@molecules/AvatarWithIdentifier/AvatarWithIdentifier' import { useChainName } from '@app/hooks/chain/useChainName' +import { useElementDimensions } from '@app/hooks/dom/useElementDimensions' import { usePrimaryName } from '@app/hooks/ensjs/public/usePrimaryName' import type { Role } from '@app/hooks/ownership/useRoles/useRoles' import { useRouterWithHistory } from '@app/hooks/useRouterWithHistory' @@ -42,6 +43,7 @@ const InnerContainer = styled.div( flex-flow: row wrap; justify-content: space-between; align-items: center; + overflow: hidden; gap: ${theme.space[4]}; `, @@ -67,6 +69,7 @@ type Props = { export const RoleRow = ({ name, address, roles, actions, isWrapped, isEmancipated }: Props) => { const router = useRouterWithHistory() const { t } = useTranslation('common') + const containerRef = useRef(null) const primary = usePrimaryName({ address: address!, enabled: !!address }) const networkName = useChainName() @@ -127,12 +130,20 @@ export const RoleRow = ({ name, address, roles, actions, isWrapped, isEmancipate const { isLoading } = primary + const { width: maxContentWidth } = useElementDimensions({ ref: containerRef }) + console.log('test', maxContentWidth) + if (!address || address === emptyAddress || isLoading) return null return ( <> - - + + {roles?.map((role) => ( diff --git a/src/components/pages/profile/[name]/tabs/PermissionsTab/OwnershipPermissions.tsx b/src/components/pages/profile/[name]/tabs/PermissionsTab/OwnershipPermissions.tsx index e9e914fc1..7012ef0be 100644 --- a/src/components/pages/profile/[name]/tabs/PermissionsTab/OwnershipPermissions.tsx +++ b/src/components/pages/profile/[name]/tabs/PermissionsTab/OwnershipPermissions.tsx @@ -1,11 +1,11 @@ -import { useMemo } from 'react' +import { useMemo, useRef } from 'react' import { Trans, useTranslation } from 'react-i18next' import styled, { css } from 'styled-components' import { GetWrapperDataReturnType } from '@ensdomains/ensjs/public' import { Button, Typography } from '@ensdomains/thorin' -import { StyledLink } from '@app/components/@atoms/StyledLink' +import { TransComponentName } from '@app/components/@atoms/Name/Name' import type { useFusesSetDates } from '@app/hooks/fuses/useFusesSetDates' import type { useFusesStates } from '@app/hooks/fuses/useFusesStates' import { useTransactionFlow } from '@app/transaction-flow/TransactionFlowProvider' @@ -50,6 +50,9 @@ export const OwnershipPermissions = ({ }: Props) => { const { t } = useTranslation('profile') + const ownershipStatusContainerRef = useRef(null) + const editorStatusContainerRef = useRef(null) + const { usePreparedDataInput } = useTransactionFlow() const showRevokePermissionsInput = usePreparedDataInput('RevokePermissions') @@ -128,13 +131,27 @@ export const OwnershipPermissions = ({ return (
{ownershipStatus === 'parent-cannot-control' && ( - + }} + components={{ + parentLink: ( + + ), + }} /> {fusesSetDates?.PARENT_CANNOT_CONTROL && ( @@ -163,13 +180,23 @@ export const OwnershipPermissions = ({ )} {ownershipStatus === 'parent-can-control' && ( - - + + }} + components={{ + parentLink: ( + + ), + }} /> @@ -207,13 +234,27 @@ export const OwnershipPermissions = ({ )} {editorStatus === 'parent-can-change-permissions' && ( - + }} + components={{ + parentLink: ( + + ), + }} /> diff --git a/src/components/pages/profile/[name]/tabs/PermissionsTab/PermissionsTab.tsx b/src/components/pages/profile/[name]/tabs/PermissionsTab/PermissionsTab.tsx index 5080f817d..230180553 100644 --- a/src/components/pages/profile/[name]/tabs/PermissionsTab/PermissionsTab.tsx +++ b/src/components/pages/profile/[name]/tabs/PermissionsTab/PermissionsTab.tsx @@ -1,3 +1,4 @@ +import { useRef } from 'react' import { Trans, useTranslation } from 'react-i18next' import styled, { css } from 'styled-components' @@ -6,6 +7,7 @@ import { Banner } from '@ensdomains/thorin' import BaseLink from '@app/components/@atoms/BaseLink' import { CacheableComponent } from '@app/components/@atoms/CacheableComponent' +import { TransComponentName } from '@app/components/@atoms/Name/Name' import { useFusesSetDates } from '@app/hooks/fuses/useFusesSetDates' import { useFusesStates } from '@app/hooks/fuses/useFusesStates' import { useParentBasicName } from '@app/hooks/useParentBasicName' @@ -31,9 +33,19 @@ const Container = styled(CacheableComponent)( `, ) +const BannerContent = styled.div( + () => css` + overflow: hidden; + width: 100%; + position: relative; + `, +) + export const PermissionsTab = ({ name, wrapperData, isCached: isBasicCached }: Props) => { const { t } = useTranslation('profile') + const bannerRef = useRef(null) + const nameParts = name.split('.') const parentName = nameParts.slice(1).join('.') @@ -58,11 +70,14 @@ export const PermissionsTab = ({ name, wrapperData, isCached: isBasicCached }: P {showUnwrapWarning && ( - + + ]} + /> + )} diff --git a/src/components/pages/profile/[name]/tabs/PermissionsTab/Section.tsx b/src/components/pages/profile/[name]/tabs/PermissionsTab/Section.tsx index 6c9d8b71c..cc9dd1c8f 100644 --- a/src/components/pages/profile/[name]/tabs/PermissionsTab/Section.tsx +++ b/src/components/pages/profile/[name]/tabs/PermissionsTab/Section.tsx @@ -1,4 +1,4 @@ -import { PropsWithChildren } from 'react' +import { forwardRef, PropsWithChildren } from 'react' import styled, { css } from 'styled-components' import { DisabledSVG, InfoCircleSVG, Typography } from '@ensdomains/thorin' @@ -68,6 +68,7 @@ const SectionItemIcon = styled.svg<{ $color: Color }>( const SectionItemContent = styled.div( ({ theme }) => css` display: flex; + overflow: hidden; flex-direction: column; gap: ${theme.space['1']}; `, @@ -77,23 +78,20 @@ type SectionItemProps = { icon?: 'info' | 'disabled' screen?: Screen } -export const SectionItem = ({ - icon, - screen, - children, - ...props -}: PropsWithChildren) => { - return ( - - {icon === 'info' ? ( - - ) : ( - - )} - {children} - - ) -} +export const SectionItem = forwardRef>( + ({ icon, screen, children, ...props }, ref) => { + return ( + + {icon === 'info' ? ( + + ) : ( + + )} + {children} + + ) + }, +) type SectionListProps = { title: string diff --git a/src/hooks/dom/useElementDimensions.ts b/src/hooks/dom/useElementDimensions.ts new file mode 100644 index 000000000..eafbc00eb --- /dev/null +++ b/src/hooks/dom/useElementDimensions.ts @@ -0,0 +1,60 @@ +import { RefObject, useRef, useSyncExternalStore } from 'react' + +const defaultRect = { width: undefined, height: undefined, top: undefined, left: undefined } + +const getBoundingClientRect = (node?: HTMLDivElement) => { + if (!node) return defaultRect + const rect = node.getBoundingClientRect() + return { + width: Math.round(rect.width), + height: Math.round(rect.height), + top: Math.round(rect.top), + left: Math.round(rect.left), + } +} + +export const useElementDimensions = ({ + ref, + boundingRect = false, +}: { + ref: RefObject + boundingRect?: boolean +}) => { + const store = useRef<{ width?: number; height?: number; top?: number; left?: number }>( + defaultRect, + ) + return useSyncExternalStore( + (onStoreChange) => { + console.log('>>>>>>>>> SUBSCRIBE') + window.addEventListener('resize', onStoreChange) + return () => { + console.log('>>>>>>>>> UNSUBSCRIBE') + window.removeEventListener('resize', onStoreChange) + } + }, + () => { + console.log('>>>>>>>> useELEMENTDIMENSIONS') + if (!ref.current) return store.current + const rect = boundingRect + ? getBoundingClientRect(ref.current) + : { + width: ref.current.offsetWidth, + height: ref.current.offsetHeight, + top: undefined, + left: undefined, + } + if ( + store.current.width !== rect.width || + store.current.height !== rect.height || + store.current.top !== rect.top || + store.current.left !== rect.left + ) { + store.current = rect + } + return store.current + }, + () => { + return store.current + }, + ) +} diff --git a/src/pages/index.tsx b/src/pages/index.tsx index 73e5e4ba8..2ce26297c 100644 --- a/src/pages/index.tsx +++ b/src/pages/index.tsx @@ -86,6 +86,29 @@ const StyledLeadingHeading = styled(LeadingHeading)( `, ) +const Test = styled.div` + resize: both; + overflow: hidden; + max-width: 1000px; + position: relative; +` + +const Wrapper = styled.div` + width: 100%; + display: flex; + overflow: hidden; +` + +const Left = styled.div` + flex: 1; + overflow: ellipsis; +` + +const Right = styled.div` + flex: 1; + overflow: ellipsis; +` + export default function Page() { const { t } = useTranslation('common') @@ -109,6 +132,12 @@ export default function Page() { {t('description')} + + + helooworld. + something.seomt.eth + + diff --git a/src/transaction-flow/input/EditRoles/views/MainView/components/RoleCard.tsx b/src/transaction-flow/input/EditRoles/views/MainView/components/RoleCard.tsx index b4b0a0c8d..cf85a43a4 100644 --- a/src/transaction-flow/input/EditRoles/views/MainView/components/RoleCard.tsx +++ b/src/transaction-flow/input/EditRoles/views/MainView/components/RoleCard.tsx @@ -34,15 +34,24 @@ const Divider = styled.div( ) const Footer = styled.button( - () => css` + ({ theme }) => css` display: flex; + overflow: hidden; justify-content: space-between; align-items: center; + gap: ${theme.space['4']}; `, ) +const FooterLeft = styled.div( + () => css` + flex: 1; + overflow: hidden; + `, +) const FooterRight = styled.div( ({ theme }) => css` + flex: 0 0 auto; display: flex; align-items: center; gap: ${theme.space['2']}; @@ -109,7 +118,9 @@ export const RoleCard = ({ address, role, dirty, onClick }: Props) => {