Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 2 additions & 3 deletions src/components/Tooltip.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { forwardRef, CSSProperties, ReactNode } from 'react'
import { forwardRef, CSSProperties, ReactNode } from 'react'
import styles from './Tooltip.module.css'

interface TooltipProps {
Expand All @@ -25,8 +25,7 @@ interface TooltipProps {
}

/**
* Base tooltip component that handles positioning and styling.
* Used as a foundation for all tooltip variants (element metrics, info tooltips, etc.)
* Base tooltip component that handles positioning and styling
*
* Features:
* - Positioned absolutely at x,y coordinates
Expand Down
12 changes: 0 additions & 12 deletions src/components/TooltipContent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,18 +27,6 @@ export const TooltipContent: React.FC<TooltipContentProps> = ({ data }) => {
<span className={styles.value}>{data.reactComponentName}</span>
</div>
)}
{data.parentComponentName && (
<div className={styles.row}>
<span className={styles.label}>parent_component:</span>
<span className={styles.value}>{data.parentComponentName}</span>
</div>
)}
{data.firstChildComponentName && (
<div className={styles.row}>
<span className={styles.label}>child_component:</span>
<span className={styles.value}>{data.firstChildComponentName}</span>
</div>
)}
<div className={styles.divider} />
</>
)}
Expand Down
9 changes: 1 addition & 8 deletions src/components/icons/Icon.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,7 @@ type IconProps = {
pulseEnabled?: boolean
}

function Icon({
id,
onClick,
children,
buttonClassName = '',
svgClassName = '',
pulseEnabled = true,
}: IconProps) {
function Icon({ id, onClick, children, buttonClassName = '', pulseEnabled = true }: IconProps) {
const [shouldPulse, setShouldPulse] = useState(false)

const handleClick = useCallback(
Expand Down
9 changes: 6 additions & 3 deletions src/components/settings/SettingsModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -67,22 +67,25 @@ function SettingsModal({ isOpen, onClose }: SettingsModalProps) {
const isAtOrBeyondLimit = (newDepth: number, operation: 'increment' | 'decrement'): boolean => {
const atMaximum = operation === 'increment' && (newDepth >= maxDepth || depth >= maxDepth)
const atMinimum = operation === 'decrement' && (newDepth <= MIN_DEPTH || depth <= MIN_DEPTH)

return atMaximum || atMinimum
}

const isApproachingLimit = (newDepth: number, operation: 'increment' | 'decrement'): boolean => {
const approachingMaximum = operation === 'increment' && newDepth === maxDepth - 1
const approachingMinimum = operation === 'decrement' && newDepth === APPROACHING_MIN_DEPTH

return approachingMaximum || approachingMinimum
}

const getDepthInputClass = (): string => {
if (showLimitFlash) return styles.depthInputError
if (showWarningFlash) return styles.depthInputWarning

return styles.depthInputDefault
}

function adjustDepth(operation: 'increment' | 'decrement'): void {
function updateDepth(operation: 'increment' | 'decrement'): void {
const requestedDepth = calculateDepthAfterOperation(operation)
const validDepth = clampDepthToValidRange(requestedDepth)

Expand Down Expand Up @@ -175,7 +178,7 @@ function SettingsModal({ isOpen, onClose }: SettingsModalProps) {
<>
<button
className={`${rowStyles.button} ${rowStyles.depthControlButtonLeft}`}
onClick={() => adjustDepth('decrement')}
onClick={() => updateDepth('decrement')}
>
-
</button>
Expand All @@ -191,7 +194,7 @@ function SettingsModal({ isOpen, onClose }: SettingsModalProps) {
/>
<button
className={`${rowStyles.button} ${rowStyles.depthControlButtonRight}`}
onClick={() => adjustDepth('increment')}
onClick={() => updateDepth('increment')}
>
+
</button>
Expand Down
2 changes: 1 addition & 1 deletion src/constants/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
export const UI_DEPTH = 3
export const COLOR_PALETTE = 'dynamic'
export const RUIO_ENABLED = false
export const ROOT_ELEMENT_SELECTOR = '#root' // Used as fallback in auto-detection chain
export const ROOT_ELEMENT_SELECTOR = '#root' // keep: used as fallback in auto-detection chain
33 changes: 20 additions & 13 deletions src/context/RuioContextProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import React, {
useMemo,
useCallback,
} from 'react'
import { applyCommittedOutlines, calculateMaxDepth } from '@utils/outline'
import { applySelectedOutlines, calculateMaxDepth } from '@utils/outline'
import { useLocalStorageState } from '@hooks/useLocalStorageState'
import { useElementPicker, PickerTooltipData } from '@hooks/useElementPicker'
import { getElementInfo } from '@utils/elementInfo'
Expand All @@ -22,7 +22,7 @@ interface ContextProps {
setDepth: React.Dispatch<React.SetStateAction<number>>
maxDepth: number // maximum available depth in the current DOM tree

rootElement: HTMLElement | null // the root element that is selected (defaults to div.body#root)
rootElement: HTMLElement | null // the root element that is selected

isElementPickerActive: boolean // is element picker active -- aka are there hover and click events drilled into the DOM
setIsElementPickerActive: React.Dispatch<React.SetStateAction<boolean>> // toggle element picker
Expand Down Expand Up @@ -60,9 +60,10 @@ export const RuioContextProvider = ({
// local state fallback for SSR compatibility
const [maxDepth, setMaxDepth] = useState(DEFAULT_MAX_DEPTH_WITHOUT_ROOT)
const [rootElement, setRootElement] = useState<HTMLElement | null>(null)
const [persistedTooltipData, setPersistedTooltipData] = useState<
Omit<PickerTooltipData, 'x' | 'y'> | null
>(null)
const [persistedTooltipData, setPersistedTooltipData] = useState<Omit<
PickerTooltipData,
'x' | 'y'
> | null>(null)

const depth = localStorageState.depth
const setDepth = localStorageState.setDepth
Expand All @@ -71,7 +72,7 @@ export const RuioContextProvider = ({
const theme = localStorageState.theme
const setTheme = localStorageState.setTheme

// on mount restore previously selected root element from localStorage or auto-detect
// on mount restore previously selected root element from localStorage or auto-detect/falback
useEffect(() => {
const element = detectRootElement(localStorageState.rootSelector, defaultRootSelector)
if (element) {
Expand Down Expand Up @@ -99,17 +100,15 @@ export const RuioContextProvider = ({

const handleRootPicked = useCallback(
(element: HTMLElement) => {
// Capture element info for persisted tooltip
// capture element info before we apply any udpates to avoid recalculations
const elementInfo = getElementInfo(element)
setPersistedTooltipData({
...elementInfo,
currentDepth: depth,
})

// Set to null first, then to the element to force the useEffect to run
// This ensures outlines are always reapplied, even when picking the same element
// force re-render to ensure all outlines are applied (resolves weird root double select issue)
setRootElement(null)
// Use setTimeout to ensure the null state is processed before setting the new element
setTimeout(() => {
setRootElement(element)
}, 0)
Expand All @@ -124,52 +123,60 @@ export const RuioContextProvider = ({
onElementPicked: handleRootPicked,
})

// apply theme to document root
// triggers when light/dark theme changes
useEffect(() => {
if (typeof document !== 'undefined') {
document.documentElement.setAttribute('data-ruio-theme', theme)
}
}, [theme])

// react when settings or root element change by applying committed outlines
// triggers when an element root has been selected
useEffect(() => {
if (rootElement) {
applyCommittedOutlines(rootElement, depth, localStorageState.ruioEnabled, currentColorPalette)
applySelectedOutlines(rootElement, depth, localStorageState.ruioEnabled, currentColorPalette)
}
}, [depth, rootElement, localStorageState.ruioEnabled, currentColorPalette])

const contextValue = useMemo(
() => ({
ruioEnabled: localStorageState.ruioEnabled,
setRuioEnabled: localStorageState.setRuioEnabled,

depth,
setDepth,
maxDepth,

rootElement,
isElementPickerActive: elementPicker.isActive,
setIsElementPickerActive: elementPicker.setIsActive,
toggleElementPicker: elementPicker.toggle,

currentColorPalette,
setCurrentColorPalette,
theme,
setTheme,

tooltipData: elementPicker.tooltipData,
persistedTooltipData,
}),
[
localStorageState.ruioEnabled,
localStorageState.setRuioEnabled,

depth,
setDepth,
maxDepth,

rootElement,
elementPicker.isActive,
elementPicker.setIsActive,
elementPicker.toggle,

currentColorPalette,
setCurrentColorPalette,
theme,
setTheme,

elementPicker.tooltipData,
persistedTooltipData,
],
Expand Down
4 changes: 2 additions & 2 deletions src/controllers/ElementPicker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,8 @@ export const ElementPicker = (

const handleMouseOut = (event: MouseEvent) => {
const target = event.target as HTMLElement
if (isValidTarget(target)) {
onMouseOut?.()
if (isValidTarget(target) && onMouseOut) {
onMouseOut()
}
}

Expand Down
14 changes: 7 additions & 7 deletions src/hooks/useElementPicker.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { useState, useEffect, useCallback } from 'react'
import { applyPreviewOutlines, clearPreviewOutlines, clearCommittedOutlines } from '@utils/outline'
import { applyPreviewOutlines, clearPreviewOutlines, clearSelectedOutlines } from '@utils/outline'
import { ElementPicker } from '@controllers/ElementPicker'
import { debounce } from '@utils/debounce'
import { getElementInfo } from '@utils/elementInfo'
Expand Down Expand Up @@ -42,7 +42,7 @@ interface UseElementPickerReturn {
}

/**
* life cycle manager for picking elements
* react lifecycle orchestration for all things ElementPicking
* delegates all events assigning and cleanup to ElementPicker() controller
*
* @param ruioEnabled - whether the ruio UI/feature is enabled
Expand Down Expand Up @@ -71,9 +71,9 @@ export const useElementPicker = ({

useEffect(() => {
if (ruioEnabled && isActive) {
// clear any existing outlines when picker mode is activated
// cleanup any existing outline styling before making changes
clearPreviewOutlines()
clearCommittedOutlines()
clearSelectedOutlines()

const debouncedHandleMouseOver = debounce((element: HTMLElement, x: number, y: number) => {
applyPreviewOutlines(element, depth, currentColorPalette)
Expand All @@ -91,7 +91,7 @@ export const useElementPicker = ({
const debouncedHandleClick = debounce((element: HTMLElement) => {
clearPreviewOutlines()
setTooltipData(null)
setIsActive(false)
setIsActive(false) // really don't like that this setter comes from this hook instead of context...
onElementPicked(element)
}, 50)

Expand All @@ -106,7 +106,7 @@ export const useElementPicker = ({
)

return () => {
// clear preview outlines when exiting picker mode
// clear preview outlines and force tooltip to re-render when exiting picker mode
clearPreviewOutlines()
setTooltipData(null)

Expand All @@ -115,7 +115,7 @@ export const useElementPicker = ({
}
}
} else {
// clear tooltip when picker mode is disabled
// force tooltip to re-render next time ruio is enabled/active
setTooltipData(null)
}
}, [isActive, depth, ruioEnabled, currentColorPalette, onElementPicked])
Expand Down
5 changes: 4 additions & 1 deletion src/hooks/useLocalStorageState.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { getConfigValue, setConfigValueAtKey, loadConfig } from '@utils/config'
import type { ConfigLocalState } from '@utils/config'

/**
* custom hook to update the value within config.
* custom hook to update the values within config (localStorage only, for now)
* skipDefault: boolean is specifically to avoid defaulting to #root (not performant) when a user hasn't selected a root starting point
* OR to use user chose specific settings we don't want to ignore.
*/
Expand Down Expand Up @@ -50,6 +50,9 @@ interface UseLocalStorageStateReturn {
setTheme: React.Dispatch<React.SetStateAction<'light' | 'dark'>>
}

/**
* custom hook to manage parsing config values from localStorage
*/
export const useLocalStorageState = (): UseLocalStorageStateReturn => {
const [ruioEnabled, setRuioEnabled] = usePersistedConfigValue('ruioEnabled')
const [rootSelectorRaw, setRootSelectorRaw] = usePersistedConfigValue('rootElementSelector', {
Expand Down
Loading