Repository: adobe/react-spectrum
File: packages/@react-spectrum/s2/src/Skeleton.tsx (TypeScript)

Code before:
// Rendered inside <Text> to create skeleton line boxes via box-decoration-break.
export function SkeletonText({children}: {children: ReactNode}): ReactNode {
  return (
    <span
      // @ts-ignore - compatibility with React < 19
      inert={inertValue(true)}
      ref={useLoadingAnimation(true)}
      className={loadingStyle + style({
        color: 'transparent',
        boxDecorationBreak: 'clone',
        borderRadius: 'sm'
      })}>
      {children}
    </span>
  );
}

// Clones the child element and displays it with skeleton styling.
export function SkeletonWrapper({children}: {children: SkeletonElement}): ReactNode {
  let isLoading = useContext(SkeletonContext);
  let animation = useLoadingAnimation(isLoading || false);
  if (isLoading == null) {
    return children;
  }

  let childRef = 'ref' in children ? children.ref as any : children.props.ref;
  return (
    <SkeletonContext.Provider value={null}>
      {isLoading ? cloneElement(children, {
        ref: mergeRefs(childRef, animation),
        className: (children.props.className || '') + ' ' + loadingStyle,
        // @ts-ignore - compatibility with React < 19
        inert: inertValue(true)
      }) : children}
    </SkeletonContext.Provider>
  );
}

// Adds default border radius around icons when displayed in a skeleton.
export function useSkeletonIcon(styles: StyleString): StyleString {
  let isSkeleton = useContext(SkeletonContext);
  if (isSkeleton) {
    return mergeStyles(style({borderRadius: 'sm'}), styles);
  }
  return styles || '' as StyleString;
}


Review comment (bug):
Fixes crash in storybook in React 19. React emits a warning when accessing `element.ref`. This checks if that is defined as a getter.

Code after:
// Rendered inside <Text> to create skeleton line boxes via box-decoration-break.
export function SkeletonText({children}: {children: ReactNode}): ReactNode {
  return (
    <span
      // @ts-ignore - compatibility with React < 19
      inert={inertValue(true)}
      ref={useLoadingAnimation(true)}
      className={loadingStyle + style({
        color: 'transparent',
        boxDecorationBreak: 'clone',
        borderRadius: 'sm'
      })}>
      {children}
    </span>
  );
}

// Clones the child element and displays it with skeleton styling.
export function SkeletonWrapper({children}: {children: SkeletonElement}): ReactNode {
  let isLoading = useContext(SkeletonContext);
  let animation = useLoadingAnimation(isLoading || false);
  if (isLoading == null) {
    return children;
  }

  let childRef = 'ref' in children && !Object.getOwnPropertyDescriptor(children, 'ref')?.get ? children.ref as any : children.props.ref;
  return (
    <SkeletonContext.Provider value={null}>
      {isLoading ? cloneElement(children, {
        ref: mergeRefs(childRef, animation),
        className: (children.props.className || '') + ' ' + loadingStyle,
        // @ts-ignore - compatibility with React < 19
        inert: inertValue(true)
      }) : children}
    </SkeletonContext.Provider>
  );
}

// Adds default border radius around icons when displayed in a skeleton.
export function useSkeletonIcon(styles: StyleString): StyleString {
  let isSkeleton = useContext(SkeletonContext);
  if (isSkeleton) {
    return mergeStyles(style({borderRadius: 'sm'}), styles);
  }
  return styles || '' as StyleString;
}