@razorpay/blade
Version:
The Design System that powers Razorpay
253 lines (233 loc) • 12.8 kB
JavaScript
import _defineProperty from '@babel/runtime/helpers/defineProperty';
import _slicedToArray from '@babel/runtime/helpers/slicedToArray';
import _taggedTemplateLiteral from '@babel/runtime/helpers/taggedTemplateLiteral';
import { useRef, useState, useEffect, useCallback, memo } from 'react';
import { ErrorBoundary } from 'react-error-boundary';
import styled, { createGlobalStyle, keyframes } from 'styled-components';
import { useGenUI, GenUIContext } from './GenUIContext.web.js';
import { useResize } from '../../utils/useResize.js';
import { jsx, Fragment, jsxs } from 'react/jsx-runtime';
var _templateObject;
function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
var ComponentErrorFallback = function ComponentErrorFallback() {
// eslint-disable-next-line react/jsx-no-useless-fragment
return /*#__PURE__*/jsx(Fragment, {});
};
/**
* Block-level component types that should have the animated gradient border effect
*/
var BLOCK_LEVEL_COMPONENTS = new Set(['CARD', 'TABLE']);
/**
* Global styles for @property rules needed for CSS animation of custom properties
* Note: @property is required to animate CSS custom properties (interpolation)
*/
var GlobalAnimationStyles = /*#__PURE__*/createGlobalStyle(_templateObject || (_templateObject = _taggedTemplateLiteral(["\n @property --travel-x {\n syntax: '<length>';\n inherits: false;\n initial-value: 0px;\n }\n @property --travel-r {\n syntax: '<angle>';\n inherits: false;\n initial-value: 0deg;\n }\n @property --travel-x2 {\n syntax: '<length>';\n inherits: false;\n initial-value: 0px;\n }\n @property --travel-r2 {\n syntax: '<angle>';\n inherits: false;\n initial-value: 0deg;\n }\n"])));
/**
* Static keyframes using CSS variables for positions.
* Values are passed via inline style: --x-start, --x-end
* Using ~70% for edge traversal, ~30% for corner sweep (good for typical card aspect ratios)
*/
var travelX = /*#__PURE__*/keyframes(["0%{--travel-x:var(--x-start);}70%{--travel-x:var(--x-end);}100%{--travel-x:var(--x-end);}"]);
var travelR = /*#__PURE__*/keyframes(["0%{--travel-r:0deg;}70%{--travel-r:0deg;}100%{--travel-r:180deg;}"]);
var travelX2 = /*#__PURE__*/keyframes(["0%{--travel-x2:var(--x-start);}30%{--travel-x2:var(--x-start);}100%{--travel-x2:var(--x-end);}"]);
var travelR2 = /*#__PURE__*/keyframes(["0%{--travel-r2:0deg;}30%{--travel-r2:-180deg;}100%{--travel-r2:-180deg;}"]);
/** Mask reveal animation - diagonal swipe from top-left to bottom-right */
var maskReveal = /*#__PURE__*/keyframes(["0%{mask-position:100% 100%;opacity:0;}100%{mask-position:0% 0%;opacity:1;}"]);
/** Gradient shade movement - left to right, fades out at end */
var shadeMove = /*#__PURE__*/keyframes(["0%{background-position:0% 50%;opacity:1;}70%{opacity:1;}100%{background-position:100% 50%;opacity:0;}"]);
/** Container for the animated border effect — owns the vertical margin to keep the ring flush */
var AnimatedBorderContainer = /*#__PURE__*/styled.div.withConfig({
displayName: "GenUISchemaRendererweb__AnimatedBorderContainer",
componentId: "sc-1hvrt76-0"
})(["position:relative;width:100%;border-radius:12px;margin:12px 0;"]);
/**
* The animated gradient border layer - light travels along the border edges.
* Uses conic-gradient positioned at (--travel-x, 50%) with rotation --travel-r.
* The animation moves the gradient center along the horizontal axis while
* rotating it at the corners to create the illusion of perimeter traversal.
*/
var GradientBorder = /*#__PURE__*/styled.div.withConfig({
displayName: "GenUISchemaRendererweb__GradientBorder",
componentId: "sc-1hvrt76-1"
})(["position:absolute;top:0;left:0;right:0;bottom:0;border-radius:12px;background:conic-gradient( from calc(var(--travel-r) - 50deg) at var(--travel-x) 50%,transparent 0%,hsl(180 85% 65%) 3%,hsl(145 75% 45%) 5%,hsl(145 85% 70%) 7%,transparent 30%,transparent 100% ),conic-gradient( from calc(var(--travel-r2) - 50deg) at var(--travel-x2) 50%,transparent 0%,hsl(180 85% 65%) 3%,hsl(145 75% 45%) 5%,hsl(145 85% 70%) 7%,transparent 30%,transparent 100% );animation:", " 1s linear infinite,", " 1s linear infinite,", " 1s linear infinite,", " 1s linear infinite;pointer-events:none;mask:linear-gradient(#fff 0 0) content-box,linear-gradient(#fff 0 0);mask-composite:exclude;padding:1.5px;opacity:", ";transition:opacity 3s ease-out;"], travelX, travelR, travelX2, travelR2, function (_ref) {
var $fadeOut = _ref.$fadeOut;
return $fadeOut ? 0 : 1;
});
/** Content container with mask reveal animation */
var ContentContainer = /*#__PURE__*/styled.div.withConfig({
displayName: "GenUISchemaRendererweb__ContentContainer",
componentId: "sc-1hvrt76-2"
})(["position:relative;width:100%;height:100%;mask-image:linear-gradient(135deg,black 0%,black 45%,transparent 55%);mask-size:300% 300%;mask-position:100% 100%;animation:", " 2s ease-out forwards;animation-delay:0.6s;"], maskReveal);
/** Green linear gradient shade overlay */
var GradientShade = /*#__PURE__*/styled.div.withConfig({
displayName: "GenUISchemaRendererweb__GradientShade",
componentId: "sc-1hvrt76-3"
})(["position:absolute;top:0;left:0;right:0;bottom:0;border-radius:12px;pointer-events:none;z-index:5;background:linear-gradient( 90deg,transparent 0%,hsl(145 85% 50% / 0.15) 30%,hsl(155 85% 45% / 0.25) 50%,hsl(145 85% 50% / 0.15) 70%,transparent 100% );background-size:50% 100%;background-repeat:no-repeat;background-position:0% 50%;animation:", " 1.5s ease-out forwards;animation-delay:0.3s;"], shadeMove);
/**
* Animated gradient border wrapper for block-level components.
* Uses CSS variables for dynamic position values with static keyframes (best perf).
* No dynamic style injection - just pass --x-start and --x-end via inline style.
*/
var AnimatedGradientBorder = function AnimatedGradientBorder(_ref2) {
var children = _ref2.children,
onAnimationComplete = _ref2.onAnimationComplete;
var containerRef = useRef(null);
var _useState = useState(false),
_useState2 = _slicedToArray(_useState, 2),
showContent = _useState2[0],
setShowContent = _useState2[1];
// Initial default values to prevent animation from breaking before ResizeObserver fires
var _useState3 = useState({
'--x-start': '50px',
'--x-end': '500px'
}),
_useState4 = _slicedToArray(_useState3, 2),
cssVars = _useState4[0],
setCssVars = _useState4[1];
useEffect(function () {
// Trigger border fade-out after mask reveal completes (0.6s delay + 1s animation)
var timer = setTimeout(function () {
setShowContent(true);
onAnimationComplete === null || onAnimationComplete === void 0 || onAnimationComplete();
}, 1000);
return function () {
return clearTimeout(timer);
};
}, [onAnimationComplete]);
// Measure container and compute CSS variable values for animation positions.
// Ring now spans the full container (top: 0, bottom: 0), so rect.height is the ring height.
var computePositions = useCallback(function (entry) {
var _entry$contentRect = entry.contentRect,
w = _entry$contentRect.width,
height = _entry$contentRect.height;
var h = height;
if (w <= 0 || h <= 0) return;
var xMargin = Math.min(h * 0.5, w * 0.1);
var xMax = w - xMargin;
setCssVars({
'--x-start': "".concat(xMargin.toFixed(1), "px"),
'--x-end': "".concat(xMax.toFixed(1), "px")
});
}, []);
useResize(containerRef, computePositions);
return /*#__PURE__*/jsxs(Fragment, {
children: [/*#__PURE__*/jsx(GlobalAnimationStyles, {}), /*#__PURE__*/jsxs(AnimatedBorderContainer, {
ref: containerRef,
$showContent: showContent,
children: [/*#__PURE__*/jsx(GradientBorder, {
$fadeOut: showContent,
style: cssVars
}), /*#__PURE__*/jsxs(ContentContainer, {
children: [children, /*#__PURE__*/jsx(GradientShade, {})]
})]
})]
});
};
/**
* Helper to generate a stable key for a component based on its index and type
*/
var getComponentKey = function getComponentKey(component, index) {
if (!(component !== null && component !== void 0 && component.component)) {
return "empty-".concat(index);
}
return "".concat(component.component, "-").concat(index);
};
/**
* Internal component that renders a single GenUI component based on its schema
* Must be used within a GenUIProvider
*/
var ComponentRendererInner = /*#__PURE__*/memo(function (_ref3) {
var component = _ref3.component,
index = _ref3.index;
var _useGenUI = useGenUI(),
registry = _useGenUI.registry,
validComponentTypes = _useGenUI.validComponentTypes;
// Handle incomplete components during streaming
if (!(component !== null && component !== void 0 && component.component)) {
return null;
}
var componentType = component.component;
var key = getComponentKey(component, index);
// Look up the renderer in the registry
var definition = registry[componentType];
if (definition) {
var _definition$animation;
var Renderer = definition.renderer;
var isBuiltInBlockLevel = BLOCK_LEVEL_COMPONENTS.has(componentType);
var isGradientAnimation = (definition === null || definition === void 0 || (_definition$animation = definition.animation) === null || _definition$animation === void 0 ? void 0 : _definition$animation.name) === 'gradient-ring-entry';
var isBlockLevel = isBuiltInBlockLevel || isGradientAnimation;
// Block-level components get the animated gradient border effect.
// Built-in components (CARD, TABLE) carry marginY="spacing.4" so need a
// 13px inset; custom components have no external margin so use 0.
if (isBlockLevel) {
return /*#__PURE__*/jsx(AnimatedGradientBorder, {
children: /*#__PURE__*/jsx(Renderer, _objectSpread(_objectSpread({}, component), {}, {
index: index
}))
}, key);
}
return /*#__PURE__*/jsx(Renderer, _objectSpread(_objectSpread({}, component), {}, {
index: index
}), key);
}
// During streaming, we might get partial component names, ie DIV for DIVIDER or ST for STACK
// Check if the current component name is a prefix of any valid component name
var isPotentiallyValidComponentName = validComponentTypes.some(function (validName) {
return validName.startsWith(componentType);
});
if (isPotentiallyValidComponentName) {
return null;
}
console.warn("[GenUI]: Unsupported component: ".concat(componentType));
return null;
});
var ComponentRenderer = /*#__PURE__*/memo(function (_ref4) {
var component = _ref4.component,
index = _ref4.index;
return /*#__PURE__*/jsx(ErrorBoundary, {
FallbackComponent: ComponentErrorFallback,
children: /*#__PURE__*/jsx(ComponentRendererInner, {
component: component,
index: index
})
});
});
/**
* Renders an array of GenUI components
* Must be used within a GenUIProvider
*
* @example
* ```tsx
* <GenUIProvider>
* <GenUISchemaRenderer components={[...]} isAnimating={isStreaming} />
* </GenUIProvider>
* ```
*/
var GenUISchemaRenderer = /*#__PURE__*/memo(function (_ref5) {
var components = _ref5.components,
isAnimating = _ref5.isAnimating,
animateOptions = _ref5.animateOptions;
var parentContext = useGenUI();
if (!components || components.length === 0) {
return null;
}
// Create a new context value with animation state overrides
var contextValue = _objectSpread(_objectSpread({}, parentContext), {}, {
isAnimating: isAnimating !== null && isAnimating !== void 0 ? isAnimating : parentContext.isAnimating,
animateOptions: animateOptions !== null && animateOptions !== void 0 ? animateOptions : parentContext.animateOptions
});
return /*#__PURE__*/jsx(GenUIContext.Provider, {
value: contextValue,
children: /*#__PURE__*/jsx(Fragment, {
children: components.map(function (component, index) {
return /*#__PURE__*/jsx(ComponentRenderer, {
component: component,
index: index
}, getComponentKey(component, index));
})
})
});
});
export { ComponentRenderer, GenUISchemaRenderer };
//# sourceMappingURL=GenUISchemaRenderer.web.js.map