aura-glass
Version:
A comprehensive glassmorphism design system for React applications with 142+ production-ready components
290 lines (287 loc) • 10.2 kB
JavaScript
'use client';
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
import { cn } from '../../lib/utilsComprehensive.js';
import { forwardRef, useState } from 'react';
import { useMotionPreference } from '../../hooks/useMotionPreference.js';
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 { useA11yId } from '../../utils/a11y.js';
import { useGlassSound } from '../../utils/soundDesign.js';
const GlassChip = /*#__PURE__*/forwardRef(({
// TODO: Integrate ContrastGuard for table cells, list items, badges, card titles, and other text content for WCAG AA compliance
children,
variant = "default",
size = "md",
removable = false,
selected = false,
disabled = false,
clickable = false,
avatar,
icon,
removeIcon,
onRemove,
onSelect,
onClick,
respectMotionPreference = true,
className,
...props
}, ref) => {
const {
shouldAnimate
} = useMotionPreference();
const {
play
} = useGlassSound();
const [isHovered, setIsHovered] = useState(false);
const chipId = useA11yId("glass-chip");
const sizeConfig = {
xs: {
height: "h-6",
padding: "glass-px-2",
text: "glass-text-xs",
iconSize: "w-3 h-3",
avatarSize: "w-4 h-4",
gap: "glass-gap-1"
},
sm: {
height: "h-7",
padding: "glass-px-2.5",
text: "glass-text-sm",
iconSize: "w-3.5 h-3.5",
avatarSize: "w-5 h-5",
gap: "glass-gap-1.5"
},
md: {
height: "h-8",
padding: "glass-px-3",
text: "glass-text-sm",
iconSize: "w-4 h-4",
avatarSize: "w-6 h-6",
gap: "glass-gap-2"
},
lg: {
height: "h-10",
padding: "glass-px-4",
text: "glass-text-base",
iconSize: "w-5 h-5",
avatarSize: "w-7 h-7",
gap: "glass-gap-2"
}
};
const variantConfig = {
default: {
base: "bg-background/50 border-border/20 text-foreground",
selected: "bg-primary/20 border-primary/40 text-primary",
hover: "hover:bg-background/70"
},
primary: {
base: "bg-primary/10 border-primary/20 text-primary",
selected: "bg-primary/90 border-primary text-primary-foreground",
hover: "hover:bg-primary/20"
},
secondary: {
base: "bg-secondary/10 border-secondary/20 text-secondary",
selected: "bg-secondary/90 border-secondary text-secondary-foreground",
hover: "hover:bg-secondary/20"
},
success: {
base: "glass-surface-success/10 glass-border/20 glass-text-success",
selected: "glass-surface-success/90 glass-surface-success glass-text-primary",
hover: "hover:glass-surface-success/20"
},
warning: {
base: "glass-surface-warning/10 glass-border/20 glass-text-primary",
selected: "glass-surface-warning/90 glass-surface-warning glass-text-primary",
hover: "hover:glass-surface-warning/20"
},
error: {
base: "glass-surface-danger/10 glass-border/20 glass-text-danger",
selected: "glass-surface-danger/90 glass-surface-danger glass-text-primary",
hover: "hover:glass-surface-danger/20"
},
info: {
base: "glass-surface-info/10 glass-border/20 glass-text-primary",
selected: "glass-surface-info/90 glass-surface-info glass-text-primary",
hover: "hover:glass-surface-info/20"
},
outline: {
base: "bg-transparent border-border text-foreground",
selected: "bg-primary/10 border-primary text-primary",
hover: "hover:bg-background/10"
},
filled: {
base: "bg-muted border-muted glass-text-secondary",
selected: "bg-primary border-primary text-primary-foreground",
hover: "hover:bg-muted/80"
}
};
const config = sizeConfig[size];
const colors = variantConfig[variant];
const handleClick = event => {
if (disabled) return;
if (onSelect) {
onSelect(!selected);
play(selected ? "deselect" : "select");
}
if (clickable) {
onClick?.(event);
play("click");
}
};
const handleRemove = event => {
if (disabled) return;
event.stopPropagation();
onRemove?.(event);
play("remove");
};
const defaultRemoveIcon = jsx("svg", {
className: "glass-w-full glass-h-full",
fill: "none",
stroke: "currentColor",
viewBox: "0 0 24 24",
strokeWidth: 2,
children: jsx("path", {
strokeLinecap: "round",
strokeLinejoin: "round",
d: "M6 18L18 6M6 6l12 12"
})
});
const chipContent = jsxs(Fragment, {
children: [avatar && jsx("div", {
className: cn("flex-shrink-0 glass-radius-full overflow-hidden", config.avatarSize),
children: avatar
}), icon && !avatar && jsx("div", {
className: cn("flex-shrink-0", config.iconSize),
children: icon
}), jsx("span", {
className: 'glass-flex-1 glass-min-w-0 truncate font-medium',
children: children
}), removable && jsx("button", {
type: "button",
onClick: handleRemove,
disabled: disabled,
className: cn("flex-shrink-0 glass-ml-1 glass-radius-full transition-colors", "hover:bg-current/20 focus:outline-none focus:ring-1 focus:ring-current", "flex items-center justify-center", config.iconSize, disabled && "opacity-50 cursor-not-allowed", "glass-focus glass-touch-target glass-contrast-guard"),
"aria-label": "Remove chip",
children: removeIcon || defaultRemoveIcon
}), jsx("div", {
className: 'absolute inset-0 glass-radius-full glass-gradient-primary glass-gradient-primary via-transparent glass-gradient-primary pointer-events-none'
})]
});
const isInteractive = clickable || onSelect || removable;
return jsx(MotionFramer, {
"data-glass-component": true,
preset: shouldAnimate && respectMotionPreference ? "scaleIn" : "none",
children: jsx(OptimizedGlassCore, {
ref: ref,
id: chipId,
elevation: "level1",
intensity: "medium",
depth: variant === "filled" ? 2 : 1,
tint: selected ? "primary" : "neutral",
border: variant === "outline" ? "strong" : "subtle",
animation: shouldAnimate && respectMotionPreference ? "shimmer" : "none",
performanceMode: "high",
liftOnHover: isInteractive && !disabled,
press: isInteractive ? true : false,
className: cn("glass-chip relative inline-flex items-center glass-radius-full border glass-backdrop-blur-md", "transition-all duration-200 select-none", config.height, config.padding, config.text, config.gap, selected ? colors.selected : colors.base, isInteractive && !disabled && "cursor-pointer", isInteractive && !disabled && colors.hover, isInteractive && !disabled && "hover:scale-105 active:scale-95", disabled && "opacity-50 cursor-not-allowed", className),
onClick: isInteractive ? handleClick : undefined,
onMouseEnter: () => setIsHovered(true),
onMouseLeave: () => setIsHovered(false),
role: onSelect ? "checkbox" : clickable ? "button" : "status",
tabIndex: isInteractive && !disabled ? 0 : -1,
"aria-checked": onSelect ? selected : undefined,
"aria-disabled": disabled,
onKeyDown: e => {
if ((e.key === "Enter" || e.key === " ") && isInteractive && !disabled) {
e.preventDefault();
handleClick(e);
}
},
...props,
children: chipContent
})
});
});
GlassChip.displayName = "GlassChip";
const GlassChipGroup = /*#__PURE__*/forwardRef(({
chips,
selectionMode = "none",
selectedValues,
defaultSelectedValues = [],
onChange,
onRemove,
size = "md",
variant = "default",
spacing = "normal",
wrap = true,
respectMotionPreference = true,
className,
...props
}, ref) => {
const {
shouldAnimate
} = useMotionPreference();
const [internalSelected, setInternalSelected] = useState(selectedValues || defaultSelectedValues);
const currentSelected = selectedValues !== undefined ? selectedValues : internalSelected;
const spacingConfig = {
tight: "glass-gap-1",
normal: "glass-gap-2",
relaxed: "glass-gap-3"
};
const handleChipSelect = (chipId, value, selected) => {
let newSelected;
if (selectionMode === "single") {
newSelected = selected ? [value] : [];
} else if (selectionMode === "multiple") {
if (selected) {
newSelected = [...currentSelected, value];
} else {
newSelected = currentSelected.filter(v => v !== value);
}
} else {
return; // No selection mode
}
if (selectedValues === undefined) {
setInternalSelected(newSelected);
}
onChange?.(newSelected);
};
const handleChipRemove = chipId => {
onRemove?.(chipId);
};
return jsx("div", {
ref: ref,
className: cn("glass-chip-group flex items-center", spacingConfig[spacing], wrap && "flex-wrap", className),
role: selectionMode !== "none" ? "group" : undefined,
...props,
children: chips.map((chip, index) => {
const chipValue = chip.value || chip.id;
const isSelected = selectionMode !== "none" && currentSelected.includes(chipValue);
return jsx(MotionFramer, {
preset: shouldAnimate && respectMotionPreference ? "slideIn" : "none",
delay: index * 50,
children: jsx(GlassChip, {
size: size,
variant: variant,
selected: isSelected,
disabled: chip.disabled,
clickable: selectionMode !== "none",
removable: chip.removable,
avatar: chip.avatar,
icon: chip.icon,
respectMotionPreference: respectMotionPreference,
onSelect: selectionMode !== "none" ? selected => handleChipSelect(chip.id, chipValue, selected) : undefined,
onRemove: chip.removable ? () => handleChipRemove(chip.id) : undefined,
children: chip.label
})
}, chip.id);
})
});
});
GlassChipGroup.displayName = "GlassChipGroup";
export { GlassChip, GlassChipGroup, GlassChip as default };
//# sourceMappingURL=GlassChip.js.map