@senka-ai/ui
Version:
A modern, type-safe Svelte 5 UI component library with full theme support, accessibility standards, and robust state management patterns
759 lines (758 loc) • 31.6 kB
JavaScript
/**
* Style composition utilities for consistent CSS class management
*
* These utilities help components compose CSS classes in a standardized way,
* reducing repetition and ensuring consistent patterns across all components.
*/
/**
* Compose CSS classes with base, variants, and conditional modifiers
* Filters out falsy values and joins with spaces
*/
export function composeClasses(...classes) {
return classes.filter(Boolean).join(' ');
}
/**
* Create a style composition function for a component with variants and sizes
* This is the main utility for component class composition
*/
export function createStyleComposer(config) {
return function composeStyles(options) {
const { variant, size, disabled, fullWidth, className = '', modifiers = {} } = options;
const parts = [
config.base,
variant && config.variants?.[variant],
size && config.sizes?.[size],
disabled && 'disabled:opacity-50 disabled:cursor-not-allowed',
fullWidth && 'w-full',
className,
];
// Add conditional modifiers
Object.entries(modifiers).forEach(([modifier, condition]) => {
if (condition)
parts.push(modifier);
});
return composeClasses(...parts);
};
}
/**
* Standard button style composer
* Updated to match Button component's original design
*/
export const createButtonStyles = createStyleComposer({
base: 'inline-flex items-center justify-center gap-2 font-medium transition-all duration-200 focus:outline-none cursor-pointer',
variants: {
primary: 'border-2 border-transparent hover:bg-highlight-hover bg-highlight text-white disabled:bg-neutral-disabled disabled:text-neutral-disabled',
secondary: 'bg-transparent border-2 hover:bg-highlight-light text-highlight border-highlight disabled:border-neutral-disabled disabled:text-neutral-light',
tertiary: 'bg-transparent border-2 border-transparent hover:bg-highlight-light text-highlight disabled:text-neutral-light',
},
sizes: {
xs: 'px-2 py-1 text-action-s rounded-lg',
small: 'px-3 py-1.5 text-action-s rounded-lg',
medium: 'px-4 py-2.75 text-action-m rounded-xl',
large: 'px-6 py-4 text-action-l rounded-2xl',
},
});
/**
* Standard input/form field style composer
* Updated to match TextField component's original design
*/
export const createInputStyles = createStyleComposer({
base: 'w-full px-3.25 py-3.25 text-body-m text-neutral-900 bg-neutral-50 border rounded-xl transition-all duration-200 focus:outline-none focus:ring-offset-0 placeholder:text-neutral-500',
variants: {
default: 'border-neutral-400',
focused: 'border-highlight ring-1 ring-highlight-400',
error: 'border-error-400 focus:ring-1 ring-error-400',
disabled: 'border-neutral-300 bg-neutral-100 text-neutral-500 cursor-not-allowed',
},
sizes: {
small: 'px-2.5 py-2 text-body-s rounded-lg',
medium: 'px-3.25 py-3.25 text-body-m rounded-xl',
large: 'px-4 py-4 text-body-l rounded-2xl',
},
});
/**
* Filter style composer - combines button appearance with form input border styling
* Default variant uses thin border like form inputs, other variants use button styling
*/
export const createFilterStyles = createStyleComposer({
base: 'inline-flex items-center justify-center gap-2 font-medium transition-all duration-200 focus:outline-none cursor-pointer',
variants: {
primary: 'border-2 border-transparent hover:bg-highlight-hover bg-highlight text-white disabled:bg-neutral-disabled disabled:text-neutral-disabled',
secondary: 'bg-transparent border border-neutral-400 hover:bg-highlight-light hover:border-highlight text-highlight disabled:border-neutral-disabled disabled:text-neutral-light',
tertiary: 'bg-transparent border-2 border-transparent hover:bg-highlight-light text-highlight disabled:text-neutral-light',
},
sizes: {
xs: 'px-2 py-1 text-action-s rounded-lg',
small: 'px-3 py-1.5 text-action-s rounded-lg',
medium: 'px-4 py-2.75 text-action-m rounded-xl',
large: 'px-6 py-4 text-action-l rounded-2xl',
},
});
/**
* Card style composer that matches the original Card component design exactly
*/
export function createCardStyles(options) {
const { variant, interactive = false, disabled = false, className = '' } = options;
const base = 'overflow-hidden transition-all duration-200';
const interactiveStyle = interactive && !disabled ? 'cursor-pointer hover:shadow-md' : '';
const disabledStyles = disabled ? 'opacity-50 cursor-not-allowed' : '';
const variants = {
default: 'rounded-2xl',
compact: 'rounded-xl',
};
return composeClasses(base, variants[variant], interactiveStyle, disabledStyles, className);
}
/**
* Card content style composer for consistent content area styling
*/
export function createCardContentStyles(options) {
const { variant } = options;
const base = 'bg-neutral-100 flex-1';
const padding = variant === 'default' ? 'p-6' : 'p-4';
const rounding = variant === 'default' ? 'rounded-b-2xl' : 'rounded-b-xl';
return composeClasses(base, padding, rounding);
}
/**
* Standard avatar style composer
*/
export const createAvatarStyles = createStyleComposer({
base: 'relative inline-flex items-center justify-center bg-highlight-50 text-neutral-600 font-medium',
variants: {
default: '',
bordered: 'ring-2 ring-white shadow-sm',
},
sizes: {
xs: 'h-8 w-8 text-body-s rounded-xl',
small: 'h-10 w-10 text-body-s rounded-2xl',
medium: 'h-14 w-14 text-body-m rounded-2xl',
large: 'h-20 w-20 text-body-l rounded-3xl',
},
});
/**
* Badge style composer that handles different badge types
* Updated to match Badge component's original design exactly
*/
export function createBadgeStyles(options) {
const { variant, size, type, className = '' } = options;
const base = 'inline-flex items-center justify-center font-medium';
const variants = {
default: 'bg-highlight text-white',
success: 'bg-success text-white',
warning: 'bg-warning text-white',
error: 'bg-error text-white',
};
const sizes = {
xs: type === 'dot' ? 'h-1.5 w-1.5' : 'h-3 w-3 min-w-3 text-caption-m',
small: type === 'dot' ? 'h-2 w-2' : 'h-4 w-4 min-w-4 text-caption-m',
medium: type === 'dot' ? 'h-3 w-3' : 'h-5 w-5 min-w-5 text-caption-m',
large: type === 'dot' ? 'h-4 w-4' : 'h-6 w-6 min-w-6 text-caption-m',
};
const shape = 'rounded-full';
return composeClasses(base, variants[variant], sizes[size], shape, className);
}
/**
* List item style composer that matches the original ListItem component design exactly
*/
export function createListItemStyles(options) {
const { compact = false, interactive = false, disabled = false, className = '' } = options;
const base = 'flex items-center gap-3 px-4 bg-background transition-colors duration-200';
const padding = compact ? 'py-2' : 'py-3';
const width = 'w-full text-left';
const interactiveStyle = interactive && !disabled ? 'cursor-pointer hover:bg-surface' : '';
const disabledStyles = disabled ? 'opacity-50 cursor-not-allowed' : '';
return composeClasses(base, padding, width, interactiveStyle, disabledStyles, className);
}
/**
* Tag style composer that matches the original Tag component design exactly
*/
export function createTagStyles(options) {
const { variant, interactive = false, disabled = false, className = '' } = options;
const base = 'inline-flex items-center gap-1.5 font-medium transition-all duration-200 rounded-full uppercase';
const variants = {
primary: 'bg-highlight text-white',
secondary: 'bg-transparent border border-highlight text-highlight',
tertiary: 'bg-highlight-light text-highlight',
};
const sizes = 'h-6 px-3 text-caption-m tracking-wider';
const interactiveStyles = interactive && !disabled ? 'cursor-pointer hover:opacity-80' : '';
const disabledStyles = disabled ? 'opacity-50 cursor-not-allowed' : '';
return composeClasses(base, variants[variant], sizes, interactiveStyles, disabledStyles, className);
}
/**
* Divider style composer that matches the original Divider component design exactly
*/
export function createDividerStyles(options) {
const { orientation, variant, spacing, className = '' } = options;
const base = 'border-neutral-300';
const orientations = {
horizontal: 'w-full border-t',
vertical: 'h-full border-l',
};
const variants = {
solid: '',
dashed: 'border-dashed',
dotted: 'border-dotted',
};
const spacings = {
none: '',
small: orientation === 'horizontal' ? 'my-2' : 'mx-2',
medium: orientation === 'horizontal' ? 'my-4' : 'mx-4',
large: orientation === 'horizontal' ? 'my-6' : 'mx-6',
};
return composeClasses(base, orientations[orientation], variants[variant], spacings[spacing], className);
}
/**
* Standard navigation item style composer
*/
export const createNavItemStyles = createStyleComposer({
base: 'flex items-center gap-3 font-medium transition-all duration-200 cursor-pointer',
variants: {
default: 'text-neutral-600 hover:text-highlight hover:bg-highlight-50',
active: 'text-highlight bg-highlight-100',
disabled: 'text-neutral-400 cursor-not-allowed',
},
sizes: {
small: 'px-3 py-2 text-body-s rounded-lg',
medium: 'px-4 py-3 text-body-m rounded-xl',
large: 'px-5 py-4 text-body-l rounded-xl',
},
});
/**
* Utility for conditional classes
* Useful for inline conditional styling
*/
export function conditionalClass(condition, trueClass, falseClass = '') {
return condition ? trueClass : falseClass;
}
/**
* Utility for focus ring classes
* Standardizes focus styling across components
*/
export function focusRing(color = 'highlight') {
const colors = {
highlight: 'focus:ring-2 focus:ring-highlight-100 focus:border-highlight',
error: 'focus:ring-2 focus:ring-error-400/20 focus:border-error-400',
success: 'focus:ring-2 focus:ring-success-100 focus:border-success-400',
warning: 'focus:ring-2 focus:ring-warning-100 focus:border-warning-400',
};
return colors[color];
}
/**
* Utility for transition classes
* Standardizes transitions across components
*/
export function transition(type = 'all') {
const transitions = {
colors: 'transition-colors duration-200 ease-in-out',
transform: 'transition-transform duration-200 ease-in-out',
all: 'transition-all duration-200 ease-in-out',
opacity: 'transition-opacity duration-200 ease-in-out',
};
return transitions[type];
}
/**
* Utility for disabled states
* Standardizes disabled styling across components
*/
export function disabledState() {
return 'disabled:opacity-50 disabled:cursor-not-allowed disabled:bg-neutral-100 disabled:text-neutral-400';
}
/**
* Utility for interactive states
* Standardizes hover/active states across components
*/
export function interactiveState() {
return 'cursor-pointer hover:opacity-90 active:scale-[0.98] transition-all duration-150';
}
/**
* Media container style composer for Image and Video components
* Updated to match original Image/Video component designs exactly
*/
export function createMediaContainerStyles(options) {
const { aspectRatio, rounded, className = '' } = options;
const base = 'relative overflow-hidden bg-highlight-50';
const aspectRatios = {
square: 'aspect-square',
'16:9': 'aspect-video',
'4:3': 'aspect-[4/3]',
'3:2': 'aspect-[3/2]',
auto: '',
};
const roundedClasses = {
none: '',
sm: 'rounded-sm',
md: 'rounded-md',
lg: 'rounded-lg',
xl: 'rounded-xl',
'2xl': 'rounded-2xl',
'3xl': 'rounded-3xl',
full: 'rounded-full',
};
return composeClasses(base, aspectRatios[aspectRatio], roundedClasses[rounded], className);
}
/**
* Image style composer that matches the original Image component design exactly
*/
export function createImageStyles(options) {
const { fit, loading, hasSize = false } = options;
const base = 'transition-opacity duration-300';
const fitClasses = {
cover: 'object-cover',
contain: 'object-contain',
fill: 'object-fill',
'scale-down': 'object-scale-down',
none: 'object-none',
};
const sizeClasses = hasSize ? '' : 'w-full h-full';
const opacityClass = !loading ? 'opacity-100' : 'opacity-0';
return composeClasses(base, fitClasses[fit], sizeClasses, opacityClass);
}
/**
* Video style composer that matches the original Video component design exactly
*/
export function createVideoStyles(options) {
const { loading } = options;
const base = 'w-full h-full object-cover transition-opacity duration-300';
const opacityClass = !loading ? 'opacity-100' : 'opacity-0';
return composeClasses(base, opacityClass);
}
/**
* Standard checkbox style composer
*/
export const createCheckboxStyles = createStyleComposer({
base: 'relative inline-flex items-center justify-center cursor-pointer transition-all duration-200',
variants: {
unchecked: 'bg-surface border-2 border-neutral-300 hover:border-neutral-400',
checked: 'bg-highlight border-2 border-highlight',
},
sizes: {
small: 'h-4 w-4 rounded',
medium: 'h-6 w-6 rounded-md',
large: 'h-8 w-8 rounded-lg',
},
});
/**
* Standard toggle/switch style composer
* Updated to match Toggle component's original design
*/
export const createToggleStyles = createStyleComposer({
base: 'relative inline-flex items-center cursor-pointer transition-all duration-200 rounded-full',
variants: {
unchecked: 'bg-neutral-300',
checked: 'bg-highlight',
},
sizes: {
xs: 'h-4 w-7',
small: 'h-5 w-9',
medium: 'h-7 w-12',
large: 'h-8 w-14',
},
});
/**
* Standard radio button style composer
* Updated to match RadioButton component's original design
*/
export const createRadioStyles = createStyleComposer({
base: 'relative inline-flex items-center justify-center cursor-pointer transition-all duration-200 rounded-full',
variants: {
unchecked: 'bg-surface border-2 border-neutral-300 hover:border-neutral-400',
checked: 'bg-highlight border-2 border-highlight',
},
sizes: {
small: 'h-4 w-4',
medium: 'h-6 w-6',
large: 'h-8 w-8',
},
});
/**
* Banner style composer for informative banner components
* Provides consistent styling for informational banners with different variants
*/
export function createBannerStyles(options) {
const { variant, disabled = false, clickable = false, className = '' } = options;
const base = 'flex items-start gap-4 p-5 rounded-2xl transition-all duration-200';
const variants = {
default: 'bg-neutral-50 border border-neutral-200',
info: 'bg-highlight-50 border border-highlight-200',
success: 'bg-success-50 border border-success-200',
warning: 'bg-warning-50 border border-warning-200',
error: 'bg-error-50 border border-error-200',
};
const clickableStyles = clickable && !disabled ? 'cursor-pointer hover:shadow-md active:scale-[0.99]' : '';
const disabledStyles = disabled ? 'opacity-50 cursor-not-allowed' : '';
return composeClasses(base, variants[variant], clickableStyles, disabledStyles, className);
}
/**
* Toast style composer for notification/feedback toasts
* Provides consistent styling for informational toasts with different variants
*/
export function createToastStyles(options) {
const { variant, dismissible = true, showBorder = false, singleLine = false, className = '' } = options;
const alignment = singleLine ? 'items-center' : 'items-start';
const base = `flex ${alignment} gap-3 p-4 rounded-xl transition-all duration-300`;
const variants = {
info: showBorder ? 'bg-highlight-50 border border-highlight-200' : 'bg-highlight-50',
success: showBorder ? 'bg-success-50 border border-success-200' : 'bg-success-50',
warning: showBorder ? 'bg-warning-50 border border-warning-200' : 'bg-warning-50',
error: showBorder ? 'bg-error-50 border border-error-200' : 'bg-error-50',
};
const dismissibleStyles = dismissible ? 'cursor-default' : '';
return composeClasses(base, variants[variant], dismissibleStyles, className);
}
/**
* Progress Bar style composer for progress indication components
* Provides consistent styling for progress bars with different variants
*/
export function createProgressBarStyles(options) {
const { size, color, animated = true, disabled = false, className = '' } = options;
// Container styles
const containerBase = 'flex flex-col gap-2 w-full';
const disabledStyles = disabled ? 'opacity-50 cursor-not-allowed' : '';
const container = composeClasses(containerBase, disabledStyles, className);
// Label styles
const label = 'text-body-s text-neutral-700 font-medium';
// Track styles
const trackBase = 'relative bg-neutral-200 overflow-hidden';
const trackSizes = {
small: 'h-1.5 rounded-full',
medium: 'h-2.5 rounded-full',
large: 'h-4 rounded-lg',
};
const track = composeClasses(trackBase, trackSizes[size]);
// Fill styles
const fillBase = 'h-full';
const fillColors = {
primary: 'bg-highlight',
success: 'bg-success',
warning: 'bg-warning',
error: 'bg-error',
};
const animatedClass = animated ? 'transition-all duration-300 ease-out' : '';
const fill = composeClasses(fillBase, fillColors[color], animatedClass);
return {
container,
label,
track,
fill,
};
}
/**
* Dialog style composer for modal dialog components
* Provides consistent styling for modal dialogs with backdrop and content areas
*/
export function createDialogStyles(options) {
const { disabled = false, className = '' } = options;
// Backdrop/overlay styles
const backdrop = 'fixed inset-0 z-50 flex items-center justify-center bg-neutral-900/50 backdrop-blur-sm';
// Dialog container styles
const containerBase = 'relative bg-background border border-neutral-200 rounded-2xl shadow-2xl max-w-md w-full mx-4 max-h-[90vh] overflow-hidden';
const disabledStyles = disabled ? 'opacity-50' : '';
const container = composeClasses(containerBase, disabledStyles, className);
// Close button styles
const closeButton = 'absolute top-2 right-2 p-1 text-neutral-500 hover:text-neutral-700 transition-colors duration-200';
// Content area styles
const content = 'p-6 pr-12';
// Title styles
const title = 'text-h3 font-semibold text-neutral-900 mb-3';
// Description styles
const description = 'text-body-m text-neutral-700 leading-relaxed';
// Actions/buttons area styles
const actions = 'flex gap-3 p-6 pt-0 border-neutral-100';
return {
backdrop,
container,
closeButton,
content,
title,
description,
actions,
};
}
/**
* Loader style composer for loading indicator components
* Provides consistent styling for circular progress and spinner loaders
*/
export function createLoaderStyles(options) {
const { variant, size, color, speed, disabled = false, className = '' } = options;
// Container styles
const containerBase = 'relative inline-flex flex-col items-center justify-center gap-2';
const disabledStyles = disabled ? 'opacity-50 cursor-not-allowed' : '';
const container = composeClasses(containerBase, disabledStyles, className);
// SVG size styles
const svgSizes = {
small: 'h-8 w-8',
medium: 'h-12 w-12',
large: 'h-16 w-16',
};
const svg = svgSizes[size];
// Background circle styles
const background = 'stroke-neutral-200';
// Foreground circle styles - colors and animations
const colorStyles = {
primary: 'stroke-highlight',
secondary: 'stroke-neutral-600',
success: 'stroke-success',
warning: 'stroke-warning',
error: 'stroke-error',
};
const progressStyles = variant === 'progress' ? 'transition-all duration-300 ease-out' : '';
const foreground = composeClasses(colorStyles[color], progressStyles);
// Progress text styles
const progressTextSizes = {
small: 'text-[8px]',
medium: 'text-action-s',
large: 'text-action-m',
};
const progressText = composeClasses('absolute top-0 left-0 w-full h-full flex items-center justify-center font-medium text-neutral-700 pointer-events-none', progressTextSizes[size]);
// Label styles
const label = 'text-body-s text-neutral-600 font-medium';
// Spinner animation speeds
const animationSpeeds = {
slow: 'spin 2s linear infinite',
normal: 'spin 1.5s linear infinite',
fast: 'spin 1s linear infinite',
};
const spinAnimation = variant === 'spinner' ? animationSpeeds[speed] : '';
return {
container,
svg,
background,
foreground,
progressText,
label,
spinAnimation,
};
}
/**
* IconButton style composer for icon-only button components
* Provides consistent styling for different icon button variants, colors, and sizes
*/
export function createIconButtonStyles(options) {
const { variant = 'ghost', color = 'default', size = 'medium', disabled = false, className = '' } = options;
const base = 'inline-flex items-center justify-center font-medium transition-all duration-200 focus:outline-none cursor-pointer';
// Color-specific styles for different variants
const colorVariants = {
default: {
ghost: 'hover:bg-highlight-50',
outlined: 'border border-highlight hover:border-highlight-600 hover:bg-highlight-50',
filled: 'text-white bg-highlight hover:bg-highlight-600',
},
neutral: {
ghost: 'hover:bg-neutral-100',
outlined: 'border border-neutral-300 hover:border-neutral-400 hover:bg-neutral-50',
filled: 'text-white bg-neutral-600 hover:bg-neutral-700',
},
success: {
ghost: 'hover:bg-success-50',
outlined: 'border border-success hover:border-success-400 hover:bg-success-50',
filled: 'text-white bg-success hover:bg-success-400',
},
warning: {
ghost: 'hover:bg-warning-50',
outlined: 'border border-warning hover:border-warning-400 hover:bg-warning-50',
filled: 'text-white bg-warning hover:bg-warning-400',
},
error: {
ghost: 'hover:bg-error-50',
outlined: 'border border-error hover:border-error-400 hover:bg-error-50',
filled: 'text-white bg-error hover:bg-error-400',
},
};
const sizes = {
small: 'h-8 w-8 rounded-lg',
medium: 'h-10 w-10 rounded-xl',
large: 'h-12 w-12 rounded-xl',
};
const disabledStyles = disabled ? 'disabled:opacity-50 disabled:cursor-not-allowed' : '';
return composeClasses(base, colorVariants[color][variant], sizes[size], disabledStyles, className);
}
/**
* NumberInput button style composer for increment/decrement buttons
* Provides consistent styling for button variants and layouts
*/
export function createNumberInputButtonStyles(options) {
const { layout, position, disabled = false, className = '' } = options;
const base = 'flex items-center justify-center border-2 border-transparent text-neutral-600 transition-colors duration-200 hover:bg-neutral-100 hover:text-neutral-800 disabled:cursor-not-allowed disabled:text-neutral-400 disabled:hover:bg-transparent disabled:hover:text-neutral-400';
const sizes = {
stacked: 'h-4 w-8',
horizontal: 'h-8 w-8',
split: 'h-8 w-8',
};
const roundings = {
stacked: position === 'increment' ? 'rounded-t-lg' : 'rounded-b-lg',
horizontal: 'rounded-lg',
split: 'rounded-lg',
};
const positioning = {
stacked: '',
horizontal: position === 'decrement' ? '' : 'ml-1',
split: position === 'decrement'
? 'absolute top-1/2 left-1 -translate-y-1/2'
: 'absolute top-1/2 right-1 -translate-y-1/2',
};
const disabledStyles = disabled ? 'cursor-not-allowed opacity-50' : '';
return composeClasses(base, sizes[layout], roundings[layout], positioning[layout], disabledStyles, className);
}
/**
* Slider style composer for custom slider components
* Provides consistent styling for track, fill, and thumb elements
*/
export function createSliderStyles(options) {
const { focused = false, disabled = false, className = '' } = options;
// Track styles
const trackBase = 'relative h-2 bg-neutral-200 rounded-full';
const trackState = disabled ? 'cursor-not-allowed opacity-50' : 'cursor-pointer';
const track = composeClasses(trackBase, trackState, className);
// Fill styles (progress bar)
const fillBase = 'absolute top-0 left-0 h-full bg-highlight rounded-full';
const fill = fillBase;
// Thumb styles (draggable handle)
const thumbBase = 'absolute top-1/2 -translate-y-1/2 w-6 h-6 rounded-full border-6 border-white shadow-[0_0_8px_rgba(0,0,0,0.15)]';
const thumbColor = disabled ? 'bg-neutral-300' : 'bg-highlight';
const thumbFocus = focused ? '' : '';
const thumbHover = !disabled ? 'hover:scale-110' : '';
const thumbState = disabled ? 'cursor-not-allowed' : 'cursor-grab active:cursor-grabbing';
const thumb = composeClasses(thumbBase, thumbColor, thumbFocus, thumbHover, thumbState);
return {
track,
fill,
thumb,
};
}
/**
* ActionSheet style composer for modal action sheet components
* Provides consistent styling for overlay, positioning, and animations
*/
export function createActionSheetStyles(options) {
const { open, position, animationSpeed, disabled = false, className = '' } = options;
// Animation duration mapping
const animationDurations = {
slow: 'duration-500',
normal: 'duration-300',
fast: 'duration-200',
};
// Backdrop styles
const backdrop = composeClasses('fixed inset-0 z-50 flex items-end justify-center bg-black/50 transition-opacity', animationDurations[animationSpeed], open ? 'opacity-100' : 'opacity-0 pointer-events-none', position === 'top' ? 'items-start' : 'items-end', disabled && 'pointer-events-none');
// Container styles with position-based transforms
const positionStyles = {
bottom: open ? 'translate-y-0' : 'translate-y-full',
top: open ? 'translate-y-0' : '-translate-y-full',
};
const container = composeClasses('w-full max-w-md transform rounded-t-2xl bg-background shadow-xl transition-transform', position === 'top' ? 'rounded-t-none rounded-b-2xl' : '', animationDurations[animationSpeed], positionStyles[position], disabled && 'opacity-50', className);
// Header styles
const header = 'flex items-center justify-between border-b border-default px-6 py-4';
// Title styles
const title = 'text-heading-s font-semibold text-primary';
// Content styles
const content = 'p-4';
return {
backdrop,
container,
header,
title,
content,
};
}
/**
* Star Rating style composer for rating input components
* Provides consistent styling for star rating container with different sizes and states
*/
export const createStarRatingStyles = createStyleComposer({
base: 'inline-flex items-center',
variants: {
default: '',
focused: 'ring-2 ring-highlight-200 ring-offset-2 rounded-lg',
},
sizes: {
xs: 'gap-0.5',
small: 'gap-0.5',
medium: 'gap-1',
large: 'gap-1.5',
},
});
/**
* Tooltip style composer
*/
export const createTooltipStyles = createStyleComposer({
base: 'absolute z-50 bg-neutral-800 text-neutral-50 shadow-lg pointer-events-none transition-opacity duration-200',
variants: {
visible: 'opacity-100',
hidden: 'opacity-0',
},
sizes: {
auto: 'p-3 whitespace-nowrap rounded-lg',
small: 'p-3.5 max-w-xs min-w-28 rounded-xl',
medium: 'p-4 max-w-sm min-w-36 rounded-xl',
large: 'p-4.5 max-w-md min-w-48 rounded-2xl',
},
});
/**
* LocationPin style composer for location pin and current location indicators
* Provides consistent styling for different variants and sizes with optional pulse animations
*/
export function createLocationPinStyles(options) {
const { variant, size, interactive = false, disabled = false, pulse = false, className = '' } = options;
const base = 'relative inline-flex items-center justify-center text-highlight transition-all duration-200';
const variants = {
pin: 'cursor-default',
current: 'cursor-default rounded-full',
};
const sizes = {
small: variant === 'pin' ? 'h-6 w-6' : 'h-8 w-8',
medium: variant === 'pin' ? 'h-8 w-8' : 'h-10 w-10',
large: variant === 'pin' ? 'h-10 w-10' : 'h-13 w-13',
};
// Interactive styles
const interactiveStyles = interactive && !disabled ? 'cursor-pointer hover:scale-110 active:scale-95' : '';
// Disabled styles
const disabledStyles = disabled ? 'opacity-50 cursor-not-allowed' : '';
// Pulse animation for current location variant
const pulseStyles = pulse && variant === 'current' ? 'animate-ping scale-70' : '';
// Current location specific styling - use border instead of background for ring effect
const borderWidths = {
small: 'border-8',
medium: 'border-10',
large: 'border-14',
};
const currentStyles = variant === 'current' ? `${borderWidths[size]} border-highlight-50` : '';
return composeClasses(base, variants[variant], sizes[size], interactiveStyles, disabledStyles, pulseStyles, currentStyles, className);
}
/**
* LocationPin current location inner dot styles
* Provides consistent styling for the inner solid dot of current location indicators
*/
export function createLocationPinInnerDotStyles(size) {
const base = 'rounded-full bg-current';
const sizes = {
small: 'h-2 w-2',
medium: 'h-2.75 w-2.75',
large: 'h-3 w-3',
};
return composeClasses(base, sizes[size]);
}
/**
* LocationPin current location outer ring styles for pulse animation
* Provides consistent styling for the outer pulsing ring of current location indicators
*/
export function createLocationPinOuterRingStyles(size) {
const base = 'absolute rounded-full animate-ping';
const sizes = {
small: 'h-4 w-4',
medium: 'h-6 w-6',
large: 'h-8 w-8',
};
return composeClasses(base, sizes[size]);
}
/**
* LocationPin current location outer circle styles (static border)
* Provides consistent styling for the outer circle border of current location indicators
*/
export function createLocationPinOuterCircleStyles(size) {
const base = 'absolute rounded-full border-2';
const sizes = {
small: 'h-5 w-5',
medium: 'h-6 w-6',
large: 'h-8 w-8',
};
return composeClasses(base, sizes[size]);
}