From f2f6e3d47a31383e44daf7bd28112325411cffa9 Mon Sep 17 00:00:00 2001 From: Johan Persson Date: Mon, 23 Feb 2026 15:15:45 +0100 Subject: [PATCH] refactor(ui): centralize prop resolution in useDefinition Extract prop-splitting and responsive resolution with default fallbacks from `useDefinition` into a new `resolveDefinitionProps` helper. This makes `propDefs.default` the single authoritative source of truth for default values. Previously, `useBgProvider` had a separate manual default lookup (`props.bg ?? (definition.propDefs as any).bg?.default`) that duplicated the main resolution loop. Signed-off-by: Johan Persson --- .../ui/src/hooks/useDefinition/helpers.ts | 38 +++++++++++++++- .../src/hooks/useDefinition/useDefinition.tsx | 44 +++++++------------ 2 files changed, 52 insertions(+), 30 deletions(-) diff --git a/packages/ui/src/hooks/useDefinition/helpers.ts b/packages/ui/src/hooks/useDefinition/helpers.ts index 3baf07b5af..8a990b4b6a 100644 --- a/packages/ui/src/hooks/useDefinition/helpers.ts +++ b/packages/ui/src/hooks/useDefinition/helpers.ts @@ -16,7 +16,7 @@ import { breakpoints } from '../useBreakpoint'; import { utilityClassMap } from '../../utils/utilityClassMap'; -import type { UnwrapResponsive, UtilityStyle } from './types'; +import type { ComponentConfig, UnwrapResponsive, UtilityStyle } from './types'; const namedBreakpoints = breakpoints.filter(b => b.id !== 'initial'); @@ -57,6 +57,42 @@ export function resolveResponsiveValue( return value as UnwrapResponsive; } +export function resolveDefinitionProps>( + definition: D, + props: Record, + breakpoint: string, +): { + ownPropsResolved: Record; + restProps: Record; +} { + const ownPropKeys = new Set(Object.keys(definition.propDefs)); + const utilityPropKeys = new Set(definition.utilityProps ?? []); + + const ownPropsRaw: Record = {}; + const restProps: Record = {}; + + for (const [key, value] of Object.entries(props)) { + if (ownPropKeys.has(key)) { + ownPropsRaw[key] = value; + } else if (!(utilityPropKeys as Set).has(key)) { + restProps[key] = value; + } + } + + const ownPropsResolved: Record = {}; + + for (const [key, config] of Object.entries(definition.propDefs)) { + const rawValue = ownPropsRaw[key]; + const resolvedValue = resolveResponsiveValue(rawValue, breakpoint); + const finalValue = resolvedValue ?? (config as any).default; + if (finalValue !== undefined) { + ownPropsResolved[key] = finalValue; + } + } + + return { ownPropsResolved, restProps }; +} + export function processUtilityProps( props: Record, utilityPropKeys: readonly Keys[], diff --git a/packages/ui/src/hooks/useDefinition/useDefinition.tsx b/packages/ui/src/hooks/useDefinition/useDefinition.tsx index 1ccf5ee5a4..6bbd08e340 100644 --- a/packages/ui/src/hooks/useDefinition/useDefinition.tsx +++ b/packages/ui/src/hooks/useDefinition/useDefinition.tsx @@ -18,7 +18,7 @@ import { ReactNode } from 'react'; import clsx from 'clsx'; import { useBreakpoint } from '../useBreakpoint'; import { useBgProvider, useBgConsumer, BgProvider } from '../useBg'; -import { resolveResponsiveValue, processUtilityProps } from './helpers'; +import { resolveDefinitionProps, processUtilityProps } from './helpers'; import type { ComponentConfig, UseDefinitionOptions, @@ -36,41 +36,19 @@ export function useDefinition< ): UseDefinitionResult { const { breakpoint } = useBreakpoint(); - // Provider: resolve bg and provide context for children - const providerBg = useBgProvider( - definition.bg === 'provider' - ? props.bg ?? (definition.propDefs as any).bg?.default - : undefined, + // Resolve all props centrally — applies responsive values and defaults + const { ownPropsResolved, restProps } = resolveDefinitionProps( + definition, + props, + breakpoint, ); - // Consumer: read parent context bg - const consumerBg = useBgConsumer(); - - const ownPropKeys = new Set(Object.keys(definition.propDefs)); - const utilityPropKeys = new Set(definition.utilityProps ?? []); - - const ownPropsRaw: Record = {}; - const restProps: Record = {}; - - for (const [key, value] of Object.entries(props)) { - if (ownPropKeys.has(key)) { - ownPropsRaw[key] = value; - } else if (!(utilityPropKeys as Set).has(key)) { - restProps[key] = value; - } - } - - const ownPropsResolved: Record = {}; const dataAttributes: Record = {}; for (const [key, config] of Object.entries(definition.propDefs)) { - const rawValue = ownPropsRaw[key]; - const resolvedValue = resolveResponsiveValue(rawValue, breakpoint); - const finalValue = resolvedValue ?? (config as any).default; + const finalValue = ownPropsResolved[key]; if (finalValue !== undefined) { - ownPropsResolved[key] = finalValue; - // Skip data-bg for bg prop when the provider path handles it if (key === 'bg' && definition.bg === 'provider') continue; @@ -81,6 +59,14 @@ export function useDefinition< } } + // Provider: resolve bg and provide context for children + const providerBg = useBgProvider( + definition.bg === 'provider' ? ownPropsResolved.bg : undefined, + ); + + // Consumer: read parent context bg + const consumerBg = useBgConsumer(); + // Provider: set data-bg from the resolved provider bg if (definition.bg === 'provider' && providerBg.bg !== undefined) { dataAttributes['data-bg'] = String(providerBg.bg);