UNPKG

@razorpay/blade

Version:

The Design System that powers Razorpay

253 lines (233 loc) 12.8 kB
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