@react-spectrum/s2
Version:
Spectrum 2 UI components in React
1 lines • 5.58 kB
Source Map (JSON)
{"mappings":"AC2E4B;;AAAA;EAAA;;;;;EAAA;;;;;AA6BI;EAAA;;;;EAAA;;;;;EAkCT;;;;EAAA;;;;EAAA;;;;EAAA","sources":["75d5c035b718255e","packages/@react-spectrum/s2/src/Skeleton.tsx"],"sourcesContent":["@import \"087771746f6f9bd4\";\n@import \"cdde487beeff3a0a\";\n@import \"15dee9c9bf5f0627\";\n","/*\n * Copyright 2024 Adobe. All rights reserved.\n * This file is licensed to you under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License. You may obtain a copy\n * of the License at http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software distributed under\n * the License is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS\n * OF ANY KIND, either express or implied. See the License for the specific language\n * governing permissions and limitations under the License.\n */\n\nimport {cloneElement, createContext, CSSProperties, ReactElement, ReactNode, Ref, useCallback, useContext, useRef} from 'react';\nimport {color, style} from '../style' with {type: 'macro'};\nimport {inertValue, mergeRefs} from '@react-aria/utils';\nimport {mergeStyles} from '../style/runtime';\nimport {raw} from '../style/style-macro' with {type: 'macro'};\nimport {StyleString} from '../style/types';\nimport {useMediaQuery} from '@react-spectrum/utils';\n\nexport function useLoadingAnimation(isAnimating: boolean): (element: HTMLElement | null) => void {\n let animationRef = useRef<Animation | null>(null);\n let reduceMotion = useMediaQuery('(prefers-reduced-motion: reduce)');\n return useCallback((element: HTMLElement | null) => {\n if (isAnimating && !animationRef.current && element && !reduceMotion) {\n // Use web animation API instead of CSS animations so that we can\n // synchronize it between all loading elements on the page (via startTime).\n animationRef.current = element.animate(\n [\n {backgroundPosition: '100%'},\n {backgroundPosition: '0%'}\n ],\n {\n duration: 2000,\n iterations: Infinity,\n easing: 'ease-in-out'\n }\n );\n animationRef.current.startTime = 0;\n } else if (!isAnimating && animationRef.current) {\n animationRef.current.cancel();\n animationRef.current = null;\n }\n }, [isAnimating]);\n}\n\nexport type SkeletonElement = ReactElement<{\n children: ReactNode,\n className?: string,\n ref?: Ref<HTMLElement>,\n inert?: boolean | 'true'\n}>;\n\nexport const SkeletonContext = createContext<boolean | null>(null);\nexport function useIsSkeleton(): boolean {\n return useContext(SkeletonContext) || false;\n}\n\nexport interface SkeletonProps {\n children: ReactNode,\n isLoading: boolean\n}\n\n/**\n * A Skeleton wraps around content to render it as a placeholder.\n */\nexport function Skeleton({children, isLoading}: SkeletonProps): ReactNode {\n // Disable all form components inside a skeleton.\n return (\n <SkeletonContext.Provider value={isLoading}>\n {children}\n </SkeletonContext.Provider>\n );\n}\n\nexport const loadingStyle = raw(`\n background-image: linear-gradient(to right, ${color('gray-100')} 33%, light-dark(${color('gray-25')}, ${color('gray-300')}), ${color('gray-100')} 66%);\n background-size: 300%;\n * {\n visibility: hidden;\n }\n`, 'L'); // add to a separate layer so it overrides default style macro styles\n\nexport function useSkeletonText(children: ReactNode, style: CSSProperties | undefined): [ReactNode, CSSProperties | undefined] {\n let isSkeleton = useContext(SkeletonContext);\n if (isSkeleton) {\n children = <SkeletonText>{children}</SkeletonText>;\n style = {\n ...style,\n // This ensures the ellipsis on truncated text is also hidden.\n // -webkit-text-fill-color overrides any `color` property that is also set.\n WebkitTextFillColor: 'transparent'\n };\n }\n return [children, style];\n}\n\n// Rendered inside <Text> to create skeleton line boxes via box-decoration-break.\nexport function SkeletonText({children}: {children: ReactNode}): ReactNode {\n return (\n <span\n // @ts-ignore - compatibility with React < 19\n inert={inertValue(true)}\n ref={useLoadingAnimation(true)}\n className={loadingStyle + style({\n color: 'transparent',\n boxDecorationBreak: 'clone',\n borderRadius: 'sm'\n })}>\n {children}\n </span>\n );\n}\n\n// Clones the child element and displays it with skeleton styling.\nexport function SkeletonWrapper({children}: {children: SkeletonElement}): ReactNode {\n let isLoading = useContext(SkeletonContext);\n let animation = useLoadingAnimation(isLoading || false);\n if (isLoading == null) {\n return children;\n }\n\n let childRef = 'ref' in children ? children.ref as any : children.props.ref;\n return (\n <SkeletonContext.Provider value={null}>\n {isLoading ? cloneElement(children, {\n ref: mergeRefs(childRef, animation),\n className: (children.props.className || '') + ' ' + loadingStyle,\n inert: 'true'\n }) : children}\n </SkeletonContext.Provider>\n );\n}\n\n// Adds default border radius around icons when displayed in a skeleton.\nexport function useSkeletonIcon(styles: StyleString): StyleString {\n let isSkeleton = useContext(SkeletonContext);\n if (isSkeleton) {\n return mergeStyles(style({borderRadius: 'sm'}), styles);\n }\n return styles || '' as StyleString;\n}\n"],"names":[],"version":3,"file":"Skeleton.css.map"}