aura-glass
Version:
A comprehensive glassmorphism design system for React applications with 142+ production-ready components
283 lines (280 loc) • 10.1 kB
JavaScript
'use client';
import { jsxs, jsx } from 'react/jsx-runtime';
import { cn } from '../../lib/utilsComprehensive.js';
import React, { forwardRef, useState, useEffect } from 'react';
import '../../primitives/GlassCore.js';
import '../../primitives/glass/GlassAdvanced.js';
import { OptimizedGlassCore } from '../../primitives/OptimizedGlassCore.js';
import '../../primitives/glass/OptimizedGlassAdvanced.js';
import '../../primitives/MotionNative.js';
import { MotionFramer } from '../../primitives/motion/MotionFramer.js';
import { GlassContainer } from './GlassContainer.js';
import { VStack } from './GlassStack.js';
import { useA11yId } from '../../utils/a11y.js';
import { useMotionPreferenceContext } from '../../contexts/MotionPreferenceContext.js';
/**
* GlassAppShell component
* Modern application shell with glassmorphism design
*/
const GlassAppShell = /*#__PURE__*/forwardRef(({
// TODO: Integrate ContrastGuard for any section titles, labels, and helper text for WCAG AA compliance
variant = "default",
header,
sidebar,
footer,
collapsible = true,
defaultCollapsed = false,
sidebarWidth = "md",
mobileOverlay = true,
mobileBreakpoint = 1024,
padding = "lg",
maxWidth = "full",
centered = false,
contentElevation = 0,
loading = false,
loadingComponent,
pageTransition = true,
respectMotionPreference = true,
"aria-label": ariaLabel = "Application shell",
role = "application",
className,
children,
...props
}, ref) => {
const [sidebarCollapsed, setSidebarCollapsed] = useState(defaultCollapsed);
const [sidebarOverlay, setSidebarOverlay] = useState(false);
const [isMobile, setIsMobile] = useState(false);
const shellId = useA11yId();
const {
prefersReducedMotion,
isMotionSafe
} = useMotionPreferenceContext();
const shouldRespectMotion = respectMotionPreference && !prefersReducedMotion;
// Handle responsive behavior
useEffect(() => {
const checkScreenSize = () => {
const mobile = window.innerWidth < mobileBreakpoint;
setIsMobile(mobile);
if (mobile && mobileOverlay) {
setSidebarOverlay(false);
}
};
checkScreenSize();
window.addEventListener("resize", checkScreenSize);
return () => window.removeEventListener("resize", checkScreenSize);
}, [mobileBreakpoint, mobileOverlay]);
const variantClasses = {
default: "",
floating: "glass-p-4",
minimal: "bg-transparent"
};
// Clone sidebar with props (align with GlassSidebar API)
const sidebarExtraProps = {
collapsed: isMobile ? false : sidebarCollapsed,
onCollapsedChange: setSidebarCollapsed,
width: sidebarWidth
};
if (isMobile && mobileOverlay) {
sidebarExtraProps.variant = "overlay";
sidebarExtraProps.open = sidebarOverlay;
sidebarExtraProps.onOpenChange = setSidebarOverlay;
}
const sidebarElement = sidebar ? /*#__PURE__*/React.cloneElement(sidebar, sidebarExtraProps) : null;
// Clone header with props (align with GlassHeader API)
const headerElement = header ? /*#__PURE__*/React.cloneElement(header, {
mobileMenuOpen: Boolean(isMobile && mobileOverlay && sidebarOverlay),
onMobileMenuToggle: () => setSidebarOverlay(v => !v)
}) : null;
return jsxs("div", {
ref: ref,
id: shellId,
role: role,
"aria-label": ariaLabel,
className: cn("flex h-screen overflow-hidden", "bg-gradient-to-br from-background via-background/95 to-surface/50",
// Motion preferences
shouldRespectMotion && "motion-safe:transition-all motion-reduce:transition-none", variantClasses?.[variant], className),
...props,
children: [sidebarElement, jsxs("div", {
className: 'glass-flex glass-flex-col glass-flex-1 overflow-hidden',
children: [headerElement, jsx("main", {
role: "main",
"aria-label": "Main content",
className: cn("flex-1 overflow-auto", "scrollbar-thin scrollbar-track-transparent scrollbar-thumb-border/30", "hover:scrollbar-thumb-border/50",
// Motion preferences
shouldRespectMotion && "motion-safe:transition-all motion-reduce:transition-none"),
children: loading && loadingComponent ? jsx("div", {
className: "glass-flex glass-items-center glass-justify-center glass-h-full",
children: loadingComponent
}) : jsx(GlassContainer, {
size: maxWidth,
centered: centered,
padding: padding,
glass: contentElevation > 0,
elevation: contentElevation,
radius: variant === "floating" ? "lg" : "none",
className: cn("min-h-full", variant === "floating" && "glass-my-4"),
children: pageTransition && shouldRespectMotion ? jsx(MotionFramer, {
preset: "fadeIn",
className: "glass-h-full",
children: children
}) : children
})
}), footer && jsx("footer", {
role: "contentinfo",
className: cn("flex-shrink-0 border-t border-border/20",
// Motion preferences
shouldRespectMotion && "motion-safe:transition-all motion-reduce:transition-none"),
children: footer
})]
})]
});
});
GlassAppShell.displayName = "GlassAppShell";
/**
* PageHeader component
* Consistent page header with title, description, and actions
*/
const PageHeader = /*#__PURE__*/forwardRef(({
title,
description,
breadcrumb,
actions,
variant = "default",
className,
...props
}, ref) => {
const variantClasses = {
default: "text-left",
centered: "text-center",
minimal: "text-left border-none pb-4"
};
return jsxs("div", {
ref: ref,
className: cn("glass-gap-4 pb-8 border-b border-border/20", variantClasses?.[variant], className),
...props,
children: [breadcrumb && jsx("div", {
className: "glass-text-sm glass-text-secondary",
children: Array.isArray(breadcrumb) ? jsx("nav", {
"aria-label": "Breadcrumb",
children: jsx("ol", {
className: "glass-flex glass-items-center glass-gap-2",
children: breadcrumb.map((item, index) => {
const isLast = index === breadcrumb.length - 1;
return jsxs("li", {
className: "glass-flex glass-items-center",
children: [index > 0 && jsx("span", {
className: "glass-mx-2",
children: "/"
}), item?.href && !isLast ? jsx("a", {
href: item?.href,
className: 'hover:text-primary transition-colors glass-focus glass-touch-target glass-contrast-guard',
children: item?.label
}) : jsx("span", {
className: isLast ? "text-foreground font-medium" : "",
children: item?.label
})]
}, index);
})
})
}) : breadcrumb
}), jsxs("div", {
className: cn("flex glass-gap-4", variant === "centered" ? "flex-col items-center" : "flex-col sm:flex-row sm:items-center sm:justify-between"),
children: [jsxs("div", {
className: "glass-gap-2",
children: [jsx("h1", {
className: 'glass-text-3xl font-bold text-primary',
children: title
}), description && jsx("p", {
className: cn("glass-text-lg glass-text-secondary", variant === "centered" ? "max-w-2xl" : "max-w-3xl"),
children: description
})]
}), actions && jsx("div", {
className: "glass-flex-shrink-0",
children: actions
})]
})]
});
});
PageHeader.displayName = "PageHeader";
/**
* ContentSection component
* Reusable content section with optional glassmorphism
*/
const ContentSection = /*#__PURE__*/forwardRef(({
title,
description,
actions,
children,
variant = "default",
elevation = "level1",
respectMotionPreference = true,
id,
className,
...props
}, ref) => {
const sectionId = useA11yId();
const {
prefersReducedMotion,
isMotionSafe
} = useMotionPreferenceContext();
const shouldRespectMotion = respectMotionPreference && !prefersReducedMotion;
const content = jsxs(VStack, {
space: "lg",
className: "glass-w-full",
children: [(title || description || actions) && jsxs("div", {
className: 'glass-flex glass-flex-col sm:flex-row sm:items-center sm:justify-between glass-gap-4',
children: [jsxs("div", {
className: "glass-gap-1",
children: [title && jsx("h2", {
id: `${id || sectionId}-title`,
className: 'glass-text-xl font-semibold text-primary',
children: title
}), description && jsx("p", {
className: "glass-text-secondary",
children: description
})]
}), actions && jsx("div", {
className: "glass-flex-shrink-0",
children: actions
})]
}), jsx("div", {
className: "glass-w-full",
children: children
})]
});
if (variant === "card") {
return jsx(OptimizedGlassCore, {
variant: "frosted",
elevation: typeof elevation === "number" ? `level${Math.min(5, Math.max(1, elevation + 1))}` : "level1",
intensity: "medium",
depth: 2,
tint: "neutral",
border: "subtle",
animation: shouldRespectMotion ? "shimmer" : "none",
performanceMode: "medium",
ref: ref,
id: id || sectionId,
role: "region",
"aria-labelledby": title ? `${id || sectionId}-title` : undefined,
className: cn("glass-p-6 w-full",
// Motion preferences
shouldRespectMotion && "motion-safe:transition-all motion-reduce:transition-none", className),
...props,
children: content
});
}
return jsx("section", {
ref: ref,
id: id || sectionId,
role: "region",
"aria-labelledby": title ? `${id || sectionId}-title` : undefined,
className: cn("w-full",
// Motion preferences
shouldRespectMotion && "motion-safe:transition-all motion-reduce:transition-none", className),
...props,
children: content
});
});
ContentSection.displayName = "ContentSection";
export { ContentSection, GlassAppShell, PageHeader };
//# sourceMappingURL=GlassAppShell.js.map