@carbon/ibm-products
Version:
Carbon for IBM Products
176 lines (165 loc) • 5.77 kB
JavaScript
/**
* Copyright IBM Corp. 2020, 2025
*
* This source code is licensed under the Apache-2.0 license found in the
* LICENSE file in the root directory of this source tree.
*/
'use strict';
var FeatureFlags$1 = require('@carbon/feature-flags');
var index = require('../../_virtual/index.js');
var React = require('react');
var propsHelper = require('../../global/js/utils/props-helper.js');
/**
* Our FeatureFlagContext is used alongside the FeatureFlags component to enable
* or disable feature flags in a given React tree
*/
const FeatureFlagContext = /*#__PURE__*/React.createContext(FeatureFlags$1.FeatureFlags);
/**
* Supports an object of feature flag values with the `flags` prop, merging them
* along with the current `FeatureFlagContext` to provide consumers to check if
* a feature flag is enabled or disabled in a given React tree
*/
function FeatureFlags(_ref) {
let {
children,
flags = {},
defaultPortalTargetBody = true,
enableDatagridUseInlineEdit = false,
enableDatagridUseEditableCell = false,
enableDatagridUseCustomizeColumns = false,
// exampleComponentSecondaryIcon = false,
// exampleComponentUseExample = false,
enableSidepanelResizer = false,
enableTestFlagA = false,
enableTestFlagB = false
} = _ref;
const parentScope = React.useContext(FeatureFlagContext);
const [prevParentScope, setPrevParentScope] = React.useState(parentScope);
const combinedFlags = {
'default-portal-target-body': defaultPortalTargetBody,
'enable-datagrid-useInlineEdit': enableDatagridUseInlineEdit,
'enable-datagrid-useEditableCell': enableDatagridUseEditableCell,
'enable-datagrid-useCustomizeColumns': enableDatagridUseCustomizeColumns,
// 'ExampleComponent.secondaryIcon': exampleComponentSecondaryIcon,
// 'ExampleComponent.useExample': exampleComponentUseExample,
enableSidepanelResizer: enableSidepanelResizer,
'enable-test-flag-a': enableTestFlagA,
'enable-test-flag-b': enableTestFlagB,
...flags
};
const [scope, updateScope] = React.useState(() => {
const scope = FeatureFlags$1.createScope(combinedFlags);
scope.mergeWithScope(parentScope);
return scope;
});
if (parentScope !== prevParentScope) {
const scope = FeatureFlags$1.createScope(combinedFlags);
scope.mergeWithScope(parentScope);
updateScope(scope);
setPrevParentScope(parentScope);
}
// We use a custom hook to detect if any of the keys or their values change
// for flags that are passed in. If they have changed, then we re-create the
// FeatureFlagScope using the new flags
useChangedValue(combinedFlags, isEqual, changedFlags => {
const scope = FeatureFlags$1.createScope(changedFlags);
scope.mergeWithScope(parentScope);
updateScope(scope);
});
return /*#__PURE__*/React.createElement(FeatureFlagContext.Provider, {
value: scope
}, children);
}
FeatureFlags.propTypes = {
children: index.default.node,
/**
* Provide the feature flags to enabled or disabled in the current React tree
*/
defaultPortalTargetBody: index.default.bool,
enableDatagridUseCustomizeColumns: index.default.bool,
enableDatagridUseEditableCell: index.default.bool,
enableDatagridUseInlineEdit: index.default.bool,
enableSidepanelResizer: index.default.bool,
enableTestFlagA: index.default.bool,
enableTestFlagB: index.default.bool,
// exampleComponentSecondaryIcon: PropTypes.bool,
// exampleComponentUseExample: PropTypes.bool,
flags: propsHelper.deprecateProp(index.default.objectOf(index.default.bool), 'The `flags` prop for `FeatureFlag` has been deprecated. Please pass the flags directly as props in camelCase')
};
/**
* This hook will store previous versions of the given `value` and compare the
* current value to the previous one using the `compare` function. If the
* compare function returns true, then the given `callback` is invoked in an
* effect.
*
* @param {any} value
* @param {Function} compare
* @param {Function} callback
*/
function useChangedValue(value, compare, callback) {
const initialRender = React.useRef(false);
const savedCallback = React.useRef(callback);
const [prevValue, setPrevValue] = React.useState(value);
if (!compare(prevValue, value)) {
setPrevValue(value);
}
React.useEffect(() => {
savedCallback.current = callback;
});
React.useEffect(() => {
// We only want the callback triggered after the first render
if (initialRender.current) {
savedCallback.current(prevValue);
}
}, [prevValue]);
React.useEffect(() => {
initialRender.current = true;
}, []);
}
/**
* Access whether a given flag is enabled or disabled in a given
* FeatureFlagContext
*
* @returns {boolean}
*/
function useFeatureFlag(flag) {
const scope = React.useContext(FeatureFlagContext);
return scope.enabled(flag);
}
/**
* Access all feature flag information for the given FeatureFlagContext
*
* @returns {FeatureFlagScope}
*/
function useFeatureFlags() {
return React.useContext(FeatureFlagContext);
}
/**
* Compare two objects and determine if they are equal. This is a shallow
* comparison since the objects we are comparing are objects with boolean flags
* from the flags prop in the `FeatureFlags` component
*
* @param {object} a
* @param {object} b
* @returns {boolean}
*/
function isEqual(a, b) {
if (a === b) {
return true;
}
for (const key of Object.keys(a)) {
if (a[key] !== b[key]) {
return false;
}
}
for (const key of Object.keys(b)) {
if (b[key] !== a[key]) {
return false;
}
}
return true;
}
exports.FeatureFlagContext = FeatureFlagContext;
exports.FeatureFlags = FeatureFlags;
exports.useFeatureFlag = useFeatureFlag;
exports.useFeatureFlags = useFeatureFlags;