,
@@ -36,19 +51,22 @@ export interface ComponentConfig<
propDefs: { [K in keyof P]: PropDefConfig };
// readonly for compatibility with const inference from factory
utilityProps?: readonly UtilityPropKey[];
- bg?: 'container' | 'leaf';
+ bg?: BgConfig;
}
/**
* Type constraint that validates bg props are present in the props type.
- * - If bg is 'container', P must include 'bg'
- * - If bg is 'leaf', no prop constraint (fully automatic)
+ * - Provider-only components must include 'bg' in their props
+ * - Provider+consumer components (e.g. Card) don't need a bg prop (they auto-increment)
+ * - Consumer-only components don't need a bg prop
*/
-export type BgPropsConstraint
= BgMode extends 'container'
- ? 'bg' extends keyof P
+export type BgPropsConstraint
= Bg extends { provider: true }
+ ? Bg extends { consumer: true }
+ ? {} // provider+consumer: bg prop is optional (auto-increment via defaultBg)
+ : 'bg' extends keyof P
? {}
: {
- __error: 'Bg container components must include bg in props type.';
+ __error: 'Bg provider components must include bg in props type.';
}
: {};
@@ -75,10 +93,11 @@ type ResolvedOwnProps<
[K in keyof PropDefs & keyof P]: ResolvePropType
;
};
-type ChildrenProps =
- BgMode extends 'container'
- ? { bgChildren: ReactNode; children?: never }
- : { children: ReactNode; bgChildren?: never };
+type ChildrenProps = Bg extends {
+ provider: true;
+}
+ ? { bgChildren: ReactNode; children?: never }
+ : { children: ReactNode; bgChildren?: never };
type DataAttributeKeys = {
[K in keyof PropDefs]: PropDefs[K] extends { dataAttribute: true }
diff --git a/packages/ui/src/hooks/useDefinition/useDefinition.tsx b/packages/ui/src/hooks/useDefinition/useDefinition.tsx
index e7a4caca97..15817a754c 100644
--- a/packages/ui/src/hooks/useDefinition/useDefinition.tsx
+++ b/packages/ui/src/hooks/useDefinition/useDefinition.tsx
@@ -17,7 +17,7 @@
import { ReactNode } from 'react';
import clsx from 'clsx';
import { useBreakpoint } from '../useBreakpoint';
-import { useBg, BgProvider } from '../useBg';
+import { useBgProvider, useBgConsumer, BgProvider } from '../useBg';
import { resolveResponsiveValue, processUtilityProps } from './helpers';
import type {
ComponentConfig,
@@ -36,9 +36,17 @@ export function useDefinition<
): UseDefinitionResult {
const { breakpoint } = useBreakpoint();
- const { bg: resolvedBg } = useBg(
- definition.bg ? { mode: definition.bg, bg: props.bg } : undefined,
- );
+ // Resolve the effective bg value: use the bg prop if provided,
+ // otherwise fall back to the defaultBg from the bg config
+ const effectiveBg = definition.bg?.provider
+ ? props.bg ?? definition.bg.defaultBg
+ : undefined;
+
+ // Provider: resolve bg and provide context for children
+ const providerBg = useBgProvider(effectiveBg);
+
+ // Consumer: read parent context bg
+ const consumerBg = useBgConsumer();
const ownPropKeys = new Set(Object.keys(definition.propDefs));
const utilityPropKeys = new Set(definition.utilityProps ?? []);
@@ -65,6 +73,9 @@ export function useDefinition<
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;
+
if ((config as any).dataAttribute) {
// eslint-disable-next-line no-restricted-syntax
dataAttributes[`data-${key.toLowerCase()}`] = String(finalValue);
@@ -72,11 +83,18 @@ export function useDefinition<
}
}
- // Set the bg data attribute from the resolved bg value
- // Containers use data-bg, leaf components use data-on-bg
- if (definition.bg && resolvedBg !== undefined) {
- const attrName = definition.bg === 'leaf' ? 'data-on-bg' : 'data-bg';
- dataAttributes[attrName] = String(resolvedBg);
+ // Provider-only: set data-bg (provider+consumer components use data-on-bg instead)
+ if (
+ definition.bg?.provider &&
+ !definition.bg?.consumer &&
+ providerBg.bg !== undefined
+ ) {
+ dataAttributes['data-bg'] = String(providerBg.bg);
+ }
+
+ // Consumer: set data-on-bg from the parent context
+ if (definition.bg?.consumer && consumerBg.bg !== undefined) {
+ dataAttributes['data-on-bg'] = String(consumerBg.bg);
}
const { utilityClasses, utilityStyle } = processUtilityProps>(
@@ -101,9 +119,9 @@ export function useDefinition<
let children: ReactNode | undefined;
let bgChildren: ReactNode | undefined;
- if (definition.bg === 'container') {
- bgChildren = resolvedBg ? (
- {props.children}
+ if (definition.bg?.provider) {
+ bgChildren = providerBg.bg ? (
+ {props.children}
) : (
props.children
);
@@ -115,7 +133,7 @@ export function useDefinition<
ownProps: {
classes,
...ownPropsResolved,
- ...(definition.bg === 'container' ? { bgChildren } : { children }),
+ ...(definition.bg?.provider ? { bgChildren } : { children }),
},
restProps,
dataAttributes,
diff --git a/packages/ui/src/index.ts b/packages/ui/src/index.ts
index dc920900f3..c9f2a3763c 100644
--- a/packages/ui/src/index.ts
+++ b/packages/ui/src/index.ts
@@ -64,9 +64,5 @@ export * from './types';
// Hooks
export { useBreakpoint } from './hooks/useBreakpoint';
-export { useBg, BgProvider } from './hooks/useBg';
-export type {
- BgContextValue,
- BgProviderProps,
- UseBgOptions,
-} from './hooks/useBg';
+export { useBgProvider, useBgConsumer, BgProvider } from './hooks/useBg';
+export type { BgContextValue, BgProviderProps } from './hooks/useBg';
diff --git a/packages/ui/src/types.ts b/packages/ui/src/types.ts
index 4b0b783656..d3dfd996aa 100644
--- a/packages/ui/src/types.ts
+++ b/packages/ui/src/types.ts
@@ -199,3 +199,13 @@ export type ContainerBg =
| 'danger'
| 'warning'
| 'success';
+
+/**
+ * Background values accepted by provider components.
+ *
+ * Includes all `ContainerBg` values plus `'neutral-auto'` which
+ * automatically increments the neutral level from the parent context.
+ *
+ * @public
+ */
+export type ProviderBg = ContainerBg | 'neutral-auto';