Skip to content

Commit

Permalink
[CP-3324] Implement Cursor-Based Tooltip Strategy (#2231)
Browse files Browse the repository at this point in the history
  • Loading branch information
dkarski authored Dec 11, 2024
1 parent 1ca1fd6 commit 80a9646
Show file tree
Hide file tree
Showing 7 changed files with 150 additions and 41 deletions.
9 changes: 8 additions & 1 deletion libs/generic-view/models/src/lib/segment-bar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,15 @@

import { z } from "zod"

const segmentBarItemDataSchema = z.object({
label: z.string(),
value: z.number().nonnegative(),
})

export type segmentBarItemData = z.infer<typeof segmentBarItemDataSchema>

const dataValidator = z.object({
segments: z.array(z.number().nonnegative()),
segments: z.array(segmentBarItemDataSchema),
})

export type SegmentBarData = z.infer<typeof dataValidator>
Expand Down
7 changes: 7 additions & 0 deletions libs/generic-view/models/src/lib/tooltip.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,12 @@ const tooltipPlacementSchema = z.enum([
"top-left",
])

const tooltipStrategySchema = z.enum([
"element-oriented",
"cursor",
"cursor-horizontal",
])

const tooltipOffsetSchema = z.object({
x: z.number(),
y: z.number(),
Expand All @@ -22,6 +28,7 @@ const tooltipOffsetSchema = z.object({
const configValidator = z
.object({
placement: tooltipPlacementSchema.optional(),
strategy: tooltipStrategySchema.optional(),
offset: tooltipOffsetSchema.optional(),
})
.optional()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,11 @@ import {
EntitiesLoaderConfig,
Feature,
McFileManagerData,
segmentBarItemData,
} from "generic-view/models"
import { View } from "generic-view/utils"
import { formatBytes } from "../../texts/format-bytes"
import { SEGMENTS_CONFIG_MAP } from "./storage-summary-bar"

const isEntitiesLoaderConfig = (
subview: unknown
Expand Down Expand Up @@ -62,18 +65,29 @@ const generateOtherFilesSpaceInformation = (
return {
fileCategoryOtherFilesItemNameSize: {
// TODO: Refactor to template after https://appnroll.atlassian.net/browse/CP-3275
text: `(${otherFilesSpaceInformation.spaceUsedString})`,
text: `(${formatBytes(otherFilesSpaceInformation.spaceUsedBytes, {
minUnit: "KB",
})})`,
},
}
}

const getSegmentBarItemData = (entityType: string, value: number) => {
return {
value,
label: `${SEGMENTS_CONFIG_MAP[entityType].label} (${formatBytes(value, {
minUnit: "KB",
})})`,
}
}

const generateStorageSummary = (
entityTypes: string[],
internalStorageInformation: NonNullable<
ReturnType<typeof findInternalStorageInformation>
>
) => {
const segments: number[] = []
const segments: segmentBarItemData[] = []

const dynamicSegmentValues = entityTypes
.filter(
Expand All @@ -84,7 +98,7 @@ const generateStorageSummary = (
const { spaceUsedBytes } =
internalStorageInformation.categoriesSpaceInformation[entityType]

return spaceUsedBytes
return getSegmentBarItemData(entityType, spaceUsedBytes)
})

segments.push(...dynamicSegmentValues)
Expand All @@ -93,22 +107,24 @@ const generateStorageSummary = (
internalStorageInformation.categoriesSpaceInformation["otherFiles"]

if (otherFilesSpaceInformation !== undefined) {
segments.push(
const { spaceUsedBytes } =
internalStorageInformation.categoriesSpaceInformation["otherFiles"]
.spaceUsedBytes
)

segments.push(getSegmentBarItemData("otherFiles", spaceUsedBytes))
}

const freeTotalSpaceBytes =
internalStorageInformation.totalSpaceBytes -
internalStorageInformation.usedSpaceBytes

segments.push(freeTotalSpaceBytes)
segments.push(getSegmentBarItemData("free", freeTotalSpaceBytes))

return {
// TODO: Refactor to template after https://appnroll.atlassian.net/browse/CP-3275
storageSummaryUsedText: {
text: `Used: ${internalStorageInformation.usedSpaceString}`,
text: `Used: ${formatBytes(internalStorageInformation.usedSpaceBytes, {
minUnit: "KB",
})}`,
},
storageSummaryFreeText: {
text: freeTotalSpaceBytes,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { Subview } from "generic-view/utils"
import { SegmentBarItem } from "generic-view/models"
import { color } from "./color"

const CONFIG_MAP: Record<string, SegmentBarItem> = {
export const SEGMENTS_CONFIG_MAP: Record<string, SegmentBarItem> = {
audioFiles: {
color: color.audioFiles,
label: "Music",
Expand Down Expand Up @@ -48,11 +48,11 @@ const CONFIG_MAP: Record<string, SegmentBarItem> = {

export const generateStorageSummaryBar = (entityTypes: string[]): Subview => {
const dynamicSegments: SegmentBarItem[] = entityTypes.map(
(entityType) => CONFIG_MAP[entityType]
(entityType) => SEGMENTS_CONFIG_MAP[entityType]
)
const fixedSegments: SegmentBarItem[] = [
CONFIG_MAP["otherFiles"],
CONFIG_MAP["free"],
SEGMENTS_CONFIG_MAP["otherFiles"],
SEGMENTS_CONFIG_MAP["free"],
]

return {
Expand Down
111 changes: 88 additions & 23 deletions libs/generic-view/ui/src/lib/interactive/tooltip/tooltip.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,10 @@ import { TooltipConfig } from "generic-view/models"
export const Tooltip: APIFC<undefined, TooltipConfig> & {
Anchor: typeof TooltipAnchor
Content: typeof TooltipContent
} = ({ children, config }) => {
} = ({ children, config, ...props }) => {
const {
placement = "bottom-right",
strategy = "element-oriented",
offset = {
x: 0,
y: 0,
Expand Down Expand Up @@ -65,33 +66,95 @@ export const Tooltip: APIFC<undefined, TooltipConfig> & {
content.style.right = `${right}px`
}

switch (placementVertical) {
case "top": {
bottom - contentRect.height > 0 ? moveToTop() : moveToBottom()
break
const updateTooltipPosition = () => {
const boundary = event.currentTarget.parentElement?.getBoundingClientRect();
const viewportWidth = boundary?.width || window.innerWidth
const viewportHeight = boundary?.height || window.innerHeight
const viewportTop = boundary?.top || 0
const viewportLeft = boundary?.left || 0
const cursorY = event.clientY + offset.y
const cursorX = event.clientX + offset.x

const adjustedY = Math.min(
Math.max(cursorY, viewportTop),
viewportTop + viewportHeight - contentRect.height
)

const adjustedX = Math.min(
Math.max(cursorX, viewportLeft),
viewportLeft + viewportWidth - contentRect.width
)

content.style.top = `${adjustedY}px`
content.style.left = `${adjustedX}px`
content.style.right = ""
content.style.bottom = ""
}

const updateTooltipPositionX = () => {

switch (placementVertical) {
case "top":
bottom - contentRect.height > 0 ? moveToTop() : moveToBottom()
break
case "bottom":
top + contentRect.height < viewportHeight
? moveToBottom()
: moveToTop()
break
}

const boundary = event.currentTarget.parentElement?.getBoundingClientRect();
const viewportWidth = boundary?.width || window.innerWidth
const viewportLeft = boundary?.left || 0
const cursorX = event.clientX + offset.x

const adjustedX = Math.min(
Math.max(cursorX, viewportLeft),
viewportLeft + viewportWidth - contentRect.width
)

content.style.left = `${adjustedX}px`
content.style.right = ""
}

const applyElementPositioning = () => {
switch (placementVertical) {
case "top": {
bottom - contentRect.height > 0 ? moveToTop() : moveToBottom()
break
}
case "bottom": {
top + contentRect.height < viewportHeight
? moveToBottom()
: moveToTop()
break
}
}
case "bottom": {
top + contentRect.height < viewportHeight
? moveToBottom()
: moveToTop()
break

switch (placementHorizontal) {
case "left":
viewportWidth - right - contentRect.width > 0
? moveToLeft()
: moveToRight()
break
case "right":
left + contentRect.width < viewportWidth
? moveToRight()
: moveToLeft()
break
}
}

switch (placementHorizontal) {
case "left":
viewportWidth - right - contentRect.width > 0
? moveToLeft()
: moveToRight()
break
case "right":
left + contentRect.width < viewportWidth
? moveToRight()
: moveToLeft()
break
if (strategy === "cursor") {
updateTooltipPosition()
} else if (strategy === "cursor-horizontal") {
updateTooltipPositionX()
} else {
applyElementPositioning()
}
},
[offset, placement]
[offset, placement, strategy]
)

const anchor = useMemo(() => {
Expand Down Expand Up @@ -126,7 +189,7 @@ export const Tooltip: APIFC<undefined, TooltipConfig> & {
}, [children])

return (
<Container>
<Container {...props}>
{anchor}
{content}
</Container>
Expand Down Expand Up @@ -164,6 +227,8 @@ const Content = styled.div`
`

const Anchor = styled.div`
width: 100%;
height: 100%;
cursor: pointer;
&:hover {
+ ${Content} {
Expand Down
21 changes: 17 additions & 4 deletions libs/generic-view/ui/src/lib/segment-bar/segment-bar-item.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,10 @@

import React from "react"
import styled from "styled-components"
import { ComputedSegmentBarItem } from "./compute-segment-bar-items.helper"
import { BaseGenericComponent } from "generic-view/utils"
import { Tooltip } from "../interactive/tooltip/tooltip"
import { P5 } from "../texts/paragraphs"
import { ComputedSegmentBarItem } from "./compute-segment-bar-items.helper"

interface SegmentBarItemProps extends ComputedSegmentBarItem {
isFirst: boolean
Expand All @@ -17,21 +19,32 @@ export const SegmentBarItem: BaseGenericComponent<
undefined,
SegmentBarItemProps
> = React.memo(({ color, width, left, zIndex, label, ...props }) => (
<Wrapper
<TooltipStyled
config={{
placement: "bottom-right",
strategy: "cursor-horizontal",
offset: { x: 0, y: 9 },
}}
style={{
width,
left,
zIndex,
backgroundColor: color,
}}
{...props}
/>
>
<Tooltip.Content>
<P5 $color={"grey1"}>{label}</P5>
</Tooltip.Content>
<Tooltip.Anchor />
</TooltipStyled>
))

const Wrapper = styled.div<{
const TooltipStyled = styled(Tooltip)<{
isFirst: boolean
}>`
position: absolute;
display: block;
height: 100%;
border-radius: ${(props) => (props.isFirst ? `56px` : `0 56px 56px 0`)};
`
3 changes: 2 additions & 1 deletion libs/generic-view/ui/src/lib/segment-bar/segment-bar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@ const mergeSegments = (
return config.segments.map((segment, index) => {
return {
...segment,
value: data.segments[index],
value: data.segments[index].value,
label: data.segments[index].label,
}
})
}
Expand Down

0 comments on commit 80a9646

Please sign in to comment.