feat(ui): polish core component surfaces
This commit is contained in:
@@ -1,4 +1,14 @@
|
||||
import { forwardRef, type ComponentPropsWithoutRef } from "react";
|
||||
import { useReducedMotion } from "motion/react";
|
||||
import {
|
||||
Children,
|
||||
cloneElement,
|
||||
forwardRef,
|
||||
isValidElement,
|
||||
useEffect,
|
||||
useState,
|
||||
type ComponentPropsWithoutRef,
|
||||
type CSSProperties
|
||||
} from "react";
|
||||
|
||||
import {
|
||||
emptyStateActionsVariants,
|
||||
@@ -13,6 +23,40 @@ import { cn } from "../lib/cn";
|
||||
import type { VariantProps } from "../lib/cva";
|
||||
import { createDataAttributes, createSlot } from "../lib/contracts";
|
||||
|
||||
function getIsStaticMotion() {
|
||||
if (typeof document === "undefined") {
|
||||
return false;
|
||||
}
|
||||
|
||||
return document.documentElement.dataset.motion === "static";
|
||||
}
|
||||
|
||||
function useMotionDisabled() {
|
||||
const prefersReducedMotion = useReducedMotion();
|
||||
const [isStaticMotion, setIsStaticMotion] = useState(getIsStaticMotion);
|
||||
|
||||
useEffect(() => {
|
||||
if (typeof document === "undefined") {
|
||||
return;
|
||||
}
|
||||
|
||||
const syncMotionMode = () => {
|
||||
setIsStaticMotion(getIsStaticMotion());
|
||||
};
|
||||
|
||||
syncMotionMode();
|
||||
|
||||
const observer = new MutationObserver(syncMotionMode);
|
||||
observer.observe(document.documentElement, {
|
||||
attributeFilter: ["data-motion"]
|
||||
});
|
||||
|
||||
return () => observer.disconnect();
|
||||
}, []);
|
||||
|
||||
return prefersReducedMotion || isStaticMotion;
|
||||
}
|
||||
|
||||
export type EmptyStateProps = ComponentPropsWithoutRef<"div"> &
|
||||
VariantProps<typeof emptyStateVariants>;
|
||||
|
||||
@@ -36,12 +80,20 @@ export type EmptyStateMediaProps = ComponentPropsWithoutRef<"div"> &
|
||||
|
||||
export const EmptyStateMedia = forwardRef<HTMLDivElement, EmptyStateMediaProps>(
|
||||
function EmptyStateMedia({ className, size, ...props }, ref) {
|
||||
const disableMotion = useMotionDisabled();
|
||||
const ambientMotionClassName =
|
||||
disableMotion || size === "compact"
|
||||
? undefined
|
||||
: size === "hero"
|
||||
? "motion-float-delayed will-change-transform"
|
||||
: "motion-breathe will-change-transform";
|
||||
|
||||
return (
|
||||
<div
|
||||
{...props}
|
||||
{...createSlot("media")}
|
||||
{...createDataAttributes({ size })}
|
||||
className={cn(emptyStateMediaVariants({ size }), className)}
|
||||
className={cn(emptyStateMediaVariants({ size }), ambientMotionClassName, className)}
|
||||
ref={ref}
|
||||
/>
|
||||
);
|
||||
@@ -115,7 +167,31 @@ export type EmptyStateActionsProps = ComponentPropsWithoutRef<"div"> &
|
||||
VariantProps<typeof emptyStateActionsVariants>;
|
||||
|
||||
export const EmptyStateActions = forwardRef<HTMLDivElement, EmptyStateActionsProps>(
|
||||
function EmptyStateActions({ className, layout, ...props }, ref) {
|
||||
function EmptyStateActions({ children, className, layout, ...props }, ref) {
|
||||
const disableMotion = useMotionDisabled();
|
||||
const animatedChildren = disableMotion
|
||||
? children
|
||||
: Children.map(children, (child, index) => {
|
||||
if (
|
||||
!isValidElement<{ className?: string; style?: CSSProperties }>(child) ||
|
||||
typeof child.type === "symbol"
|
||||
) {
|
||||
return child;
|
||||
}
|
||||
|
||||
return cloneElement(child, {
|
||||
className: cn(
|
||||
"[animation:aiui-slide-up-sm_var(--dur-base)_var(--ease-emphasized)_both]",
|
||||
"will-change-transform",
|
||||
child.props.className
|
||||
),
|
||||
style: {
|
||||
...(child.props.style ?? {}),
|
||||
animationDelay: `${Math.min(index * 70, 140)}ms`
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
return (
|
||||
<div
|
||||
{...props}
|
||||
@@ -123,7 +199,9 @@ export const EmptyStateActions = forwardRef<HTMLDivElement, EmptyStateActionsPro
|
||||
{...createDataAttributes({ layout })}
|
||||
className={cn(emptyStateActionsVariants({ layout }), className)}
|
||||
ref={ref}
|
||||
/>
|
||||
>
|
||||
{animatedChildren}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user