11import React from 'react' ;
22import { useFlag } from '../evaluation' ;
33import type { FlagQuery } from '../query' ;
4+ import type { FlagValue , EvaluationDetails } from '@openfeature/core' ;
45
56/**
6- * Props for the Feature component that conditionally renders content based on feature flag state.
7- * @interface FeatureProps
7+ * Default predicate function that checks if the expected value equals the actual flag value.
8+ * @param {T } expected The expected value to match against
9+ * @param {EvaluationDetails<T> } actual The evaluation details containing the actual flag value
10+ * @returns {boolean } true if the values match, false otherwise
811 */
9- interface FeatureProps {
12+ function equals < T extends FlagValue > ( expected : T , actual : EvaluationDetails < T > ) : boolean {
13+ return expected === actual . value ;
14+ }
15+
16+ /**
17+ * Props for the FeatureFlag component that conditionally renders content based on feature flag state.
18+ * @interface FeatureFlagProps
19+ */
20+ interface FeatureFlagProps < T extends FlagValue = FlagValue > {
1021 /**
1122 * The key of the feature flag to evaluate.
1223 */
13- featureKey : string ;
24+ flagKey : string ;
1425
1526 /**
1627 * Optional value to match against the feature flag value.
1728 * If provided, the component will only render children when the flag value matches this value.
1829 * If a boolean, it will check if the flag is enabled (true) or disabled (false).
1930 * If a string, it will check if the flag variant equals this string.
2031 */
21- match ?: string | boolean ;
32+ match ?: T ;
33+
34+ /**
35+ * Optional predicate function for custom matching logic.
36+ * If provided, this function will be used instead of the default equality check.
37+ * @param expected The expected value (from match prop)
38+ * @param actual The evaluation details
39+ * @returns true if the condition is met, false otherwise
40+ */
41+ predicate ?: ( expected : T | undefined , actual : EvaluationDetails < T > ) => boolean ;
2242
2343 /**
2444 * Default value to use when the feature flag is not found.
2545 */
26- // eslint-disable-next-line @typescript-eslint/no-explicit-any
27- defaultValue : any ;
46+ defaultValue : T ;
2847
2948 /**
3049 * Content to render when the feature flag condition is met.
3150 * Can be a React node or a function that receives flag query details and returns a React node.
3251 */
33- // eslint-disable-next-line @typescript-eslint/no-explicit-any
34- children : React . ReactNode | ( ( details : FlagQuery < any > ) => React . ReactNode ) ;
52+ children : React . ReactNode | ( ( details : FlagQuery < T > ) => React . ReactNode ) ;
3553
3654 /**
3755 * Optional content to render when the feature flag condition is not met.
3856 */
3957 fallback ?: React . ReactNode ;
40-
41- /**
42- * If true, inverts the condition logic (renders children when condition is NOT met).
43- */
44- negate ?: boolean ;
4558}
4659
4760/**
4861 * FeatureFlag component that conditionally renders its children based on the evaluation of a feature flag.
49- *
50- * @param {FeatureProps } props The properties for the FeatureFlag component.
62+ * @param {FeatureFlagProps } props The properties for the FeatureFlag component.
5163 * @returns {React.ReactElement | null } The rendered component or null if the feature is not enabled.
5264 */
53- export function FeatureFlag ( {
54- featureKey ,
65+ export function FeatureFlag < T extends FlagValue = FlagValue > ( {
66+ flagKey ,
5567 match,
56- negate = false ,
57- defaultValue = true ,
68+ predicate ,
69+ defaultValue,
5870 children,
5971 fallback = null ,
60- } : FeatureProps ) : React . ReactElement | null {
61- const details = useFlag ( featureKey , defaultValue , {
72+ } : FeatureFlagProps < T > ) : React . ReactElement | null {
73+ const details = useFlag ( flagKey , defaultValue , {
6274 updateOnContextChanged : true ,
6375 } ) ;
6476
@@ -67,23 +79,20 @@ export function FeatureFlag({
6779 return < > { fallback } </ > ;
6880 }
6981
70- let isMatch = false ;
71- if ( typeof match === 'string' ) {
72- isMatch = details . variant === match ;
73- } else if ( typeof match !== 'undefined' ) {
74- isMatch = details . value === match ;
82+ // Use custom predicate if provided, otherwise use default matching logic
83+ let shouldRender = false ;
84+ if ( predicate ) {
85+ shouldRender = predicate ( match , details . details as EvaluationDetails < T > ) ;
86+ } else if ( match !== undefined ) {
87+ // Default behavior: check if match value equals flag value
88+ shouldRender = equals ( match , details . details as EvaluationDetails < T > ) ;
89+ } else {
90+ // If no match value is provided, render if flag is truthy
91+ shouldRender = Boolean ( details . value ) ;
7592 }
7693
77- // If match is undefined, we assume the flag is enabled
78- if ( match === void 0 ) {
79- isMatch = true ;
80- }
81-
82- const shouldRender = negate ? ! isMatch : isMatch ;
83-
8494 if ( shouldRender ) {
85- console . log ( 'chop chop' ) ;
86- const childNode : React . ReactNode = typeof children === 'function' ? children ( details ) : children ;
95+ const childNode : React . ReactNode = typeof children === 'function' ? children ( details as FlagQuery < T > ) : children ;
8796 return < > { childNode } </ > ;
8897 }
8998
0 commit comments