Update various color/style improvements, primarily for error/warn/info bubbles and boxes
This commit is contained in:
161
docs/README.CSS_UTILITY_CLASSES.md
Normal file
161
docs/README.CSS_UTILITY_CLASSES.md
Normal file
@@ -0,0 +1,161 @@
|
|||||||
|
# Custom CSS Utility Classes
|
||||||
|
|
||||||
|
This document describes the centralized CSS utility classes defined in `src/styles/global.css` for consistent styling across the application.
|
||||||
|
|
||||||
|
## Error & Warning Classes
|
||||||
|
|
||||||
|
### Validation Bubbles (Popups)
|
||||||
|
|
||||||
|
**`.error-bubble`**
|
||||||
|
- Used for error validation popup messages on form fields
|
||||||
|
- Light mode: Soft red background with dark red text
|
||||||
|
- Dark mode: Very dark red background (80% opacity) with light red text
|
||||||
|
- Includes border for visual separation
|
||||||
|
- Example: Input field validation errors
|
||||||
|
|
||||||
|
**`.warning-bubble`**
|
||||||
|
- Used for warning validation popup messages on form fields
|
||||||
|
- Light mode: Soft amber background with dark amber text
|
||||||
|
- Dark mode: Very dark amber background (80% opacity) with light amber text
|
||||||
|
- Includes border for visual separation
|
||||||
|
- Example: Input field warnings about unusual values
|
||||||
|
|
||||||
|
### Borders
|
||||||
|
|
||||||
|
**`.error-border`**
|
||||||
|
- Red border for form inputs with errors
|
||||||
|
- Uses the `destructive` color from the theme
|
||||||
|
- Example: Highlight invalid input fields
|
||||||
|
|
||||||
|
**`.warning-border`**
|
||||||
|
- Amber border for form inputs with warnings
|
||||||
|
- Uses `amber-500` color
|
||||||
|
- Example: Highlight input fields with unusual but valid values
|
||||||
|
|
||||||
|
### Background Boxes (Static Sections)
|
||||||
|
|
||||||
|
**`.error-bg-box`**
|
||||||
|
- For static error information sections
|
||||||
|
- Light mode: Light red background
|
||||||
|
- Dark mode: Dark red background (40% opacity)
|
||||||
|
- Includes border
|
||||||
|
- Example: Persistent error messages in modals
|
||||||
|
|
||||||
|
**`.warning-bg-box`**
|
||||||
|
- For static warning information sections
|
||||||
|
- Light mode: Light amber background
|
||||||
|
- Dark mode: Dark amber background (40% opacity)
|
||||||
|
- Includes border
|
||||||
|
- Example: Warning boxes in settings
|
||||||
|
|
||||||
|
**`.info-bg-box`**
|
||||||
|
- For informational sections
|
||||||
|
- Light mode: Light blue background
|
||||||
|
- Dark mode: Dark blue background (40% opacity)
|
||||||
|
- Includes border
|
||||||
|
- Example: Helpful tips, contextual information
|
||||||
|
|
||||||
|
### Text Colors
|
||||||
|
|
||||||
|
**`.error-text`**
|
||||||
|
- Dark red text in light mode, light red in dark mode
|
||||||
|
- High contrast for readability
|
||||||
|
- Example: Inline error messages
|
||||||
|
|
||||||
|
**`.warning-text`**
|
||||||
|
- Dark amber text in light mode, light amber in dark mode
|
||||||
|
- High contrast for readability
|
||||||
|
- Example: Inline warning messages
|
||||||
|
|
||||||
|
**`.info-text`**
|
||||||
|
- Dark blue text in light mode, light blue in dark mode
|
||||||
|
- High contrast for readability
|
||||||
|
- Example: Inline informational text
|
||||||
|
|
||||||
|
## Usage Examples
|
||||||
|
|
||||||
|
### Form Validation Popup
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
{hasError && (
|
||||||
|
<div className="error-bubble w-80 text-xs p-2 rounded-md">
|
||||||
|
{errorMessage}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{hasWarning && (
|
||||||
|
<div className="warning-bubble w-80 text-xs p-2 rounded-md">
|
||||||
|
{warningMessage}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Input Field Borders
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
<Input
|
||||||
|
className={cn(
|
||||||
|
hasError && "error-border",
|
||||||
|
hasWarning && !hasError && "warning-border"
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Static Information Boxes
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
{/* Warning box */}
|
||||||
|
<div className="warning-bg-box rounded-md p-3">
|
||||||
|
<p className="warning-text">{warningText}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Info box */}
|
||||||
|
<div className="info-bg-box rounded-md p-3">
|
||||||
|
<p className="info-text">{infoText}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Error box */}
|
||||||
|
<div className="error-bg-box rounded-md p-3">
|
||||||
|
<p className="error-text">{errorText}</p>
|
||||||
|
</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
## Accessibility
|
||||||
|
|
||||||
|
All classes are designed with accessibility in mind:
|
||||||
|
|
||||||
|
- ✅ **High contrast ratios** - Meet WCAG AA standards for text readability
|
||||||
|
- ✅ **Dark mode optimized** - Reduced saturation and brightness in dark mode (80% opacity for bubbles, 40% for boxes)
|
||||||
|
- ✅ **Consistent theming** - Semantic color usage (red=error, amber=warning, blue=info)
|
||||||
|
- ✅ **Icon visibility** - Muted backgrounds ensure icons stand out
|
||||||
|
- ✅ **Border separation** - Clear visual boundaries between elements
|
||||||
|
|
||||||
|
## Opacity Rationale
|
||||||
|
|
||||||
|
- **Validation bubbles**: 80% opacity in dark mode - Higher opacity for better text readability during focused interaction
|
||||||
|
- **Background boxes**: 40% opacity in dark mode - Lower opacity for persistent elements to reduce visual weight
|
||||||
|
|
||||||
|
## Migration Guide
|
||||||
|
|
||||||
|
When updating existing code to use these classes:
|
||||||
|
|
||||||
|
**Before:**
|
||||||
|
```tsx
|
||||||
|
className="bg-red-50 dark:bg-red-950/50 text-red-900 dark:text-red-200 border border-red-300 dark:border-red-800"
|
||||||
|
```
|
||||||
|
|
||||||
|
**After:**
|
||||||
|
```tsx
|
||||||
|
className="error-bubble"
|
||||||
|
```
|
||||||
|
|
||||||
|
This reduces duplication, ensures consistency, and makes it easier to update the design system in the future.
|
||||||
|
|
||||||
|
## Files Using These Classes
|
||||||
|
|
||||||
|
- `src/components/ui/form-numeric-input.tsx`
|
||||||
|
- `src/components/ui/form-time-input.tsx`
|
||||||
|
- `src/components/settings.tsx`
|
||||||
|
- `src/components/data-management-modal.tsx`
|
||||||
|
- `src/components/disclaimer-modal.tsx`
|
||||||
|
- `src/components/day-schedule.tsx`
|
||||||
@@ -320,7 +320,7 @@ const MedPlanAssistant = () => {
|
|||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<footer className="mt-8 p-4 bg-muted rounded-lg text-sm text-muted-foreground border">
|
<footer className="mt-8 p-4 bg-muted rounded-lg text-sm border">
|
||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
<div>
|
<div>
|
||||||
<h3 className="font-semibold mb-2 text-foreground">{t('importantNote')}</h3>
|
<h3 className="font-semibold mb-2 text-foreground">{t('importantNote')}</h3>
|
||||||
|
|||||||
@@ -843,7 +843,7 @@ const DataManagementModal: React.FC<DataManagementModalProps> = ({
|
|||||||
className={`flex items-center gap-2 text-sm ${
|
className={`flex items-center gap-2 text-sm ${
|
||||||
jsonValidationMessage.type === 'success'
|
jsonValidationMessage.type === 'success'
|
||||||
? 'text-green-600 dark:text-green-400'
|
? 'text-green-600 dark:text-green-400'
|
||||||
: 'text-red-600 dark:text-red-400'
|
: 'error-text'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
{jsonValidationMessage.type === 'success' ? (
|
{jsonValidationMessage.type === 'success' ? (
|
||||||
@@ -858,7 +858,7 @@ const DataManagementModal: React.FC<DataManagementModalProps> = ({
|
|||||||
{jsonValidationMessage.warnings.map((warning, index) => (
|
{jsonValidationMessage.warnings.map((warning, index) => (
|
||||||
<div
|
<div
|
||||||
key={index}
|
key={index}
|
||||||
className="bg-yellow-500 text-white text-xs p-2 rounded-md"
|
className="warning-bubble text-xs p-2 rounded-md"
|
||||||
>
|
>
|
||||||
{warning}
|
{warning}
|
||||||
</div>
|
</div>
|
||||||
@@ -909,7 +909,7 @@ const DataManagementModal: React.FC<DataManagementModalProps> = ({
|
|||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<Trash2 className="h-5 w-5 text-destructive" />
|
<Trash2 className="h-5 w-5 text-destructive" />
|
||||||
</div>
|
</div>
|
||||||
<h3 className="text-lg font-semibold text-destructive">{t('deleteSpecificData')}</h3>
|
<h3 className="text-lg font-semibold">{t('deleteSpecificData')}</h3>
|
||||||
<Tooltip>
|
<Tooltip>
|
||||||
<TooltipTrigger asChild>
|
<TooltipTrigger asChild>
|
||||||
<button
|
<button
|
||||||
@@ -926,8 +926,8 @@ const DataManagementModal: React.FC<DataManagementModalProps> = ({
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Warning Message */}
|
{/* Warning Message */}
|
||||||
<div className="bg-yellow-50 dark:bg-yellow-900/20 border border-yellow-200 dark:border-yellow-800 rounded-md p-3 text-sm">
|
<div className="warning-bg-box rounded-md p-3 text-sm">
|
||||||
<p className="text-yellow-800 dark:text-yellow-200">{t('deleteDataWarning')}</p>
|
<p className="warning-text">{t('deleteDataWarning')}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
|
|||||||
@@ -135,7 +135,7 @@ const DaySchedule: React.FC<DayScheduleProps> = ({
|
|||||||
tooltip={t('removeDay')}
|
tooltip={t('removeDay')}
|
||||||
size="sm"
|
size="sm"
|
||||||
variant="outline"
|
variant="outline"
|
||||||
className="border-destructive text-destructive hover:bg-destructive hover:text-destructive-foreground"
|
className="text-destructive hover:bg-destructive hover:text-destructive-foreground"
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
@@ -153,7 +153,7 @@ const DaySchedule: React.FC<DayScheduleProps> = ({
|
|||||||
>
|
>
|
||||||
<Badge
|
<Badge
|
||||||
variant="outline"
|
variant="outline"
|
||||||
className={`text-xs ${doseCountDiff > 0 ? 'bg-blue-100 dark:bg-blue-900/40 dark:text-blue-200' : 'bg-orange-100 dark:bg-orange-900/40 dark:text-orange-200'}`}
|
className={`text-xs ${doseCountDiff > 0 ? 'bg-blue-100 dark:bg-blue-900/60 dark:text-blue-200' : 'bg-orange-100 dark:bg-orange-900/60 dark:text-orange-200'}`}
|
||||||
>
|
>
|
||||||
{doseCountDiff > 0 ? <TrendingUp className="h-3 w-3 inline mr-1" /> : <TrendingDown className="h-3 w-3 inline mr-1" />}
|
{doseCountDiff > 0 ? <TrendingUp className="h-3 w-3 inline mr-1" /> : <TrendingDown className="h-3 w-3 inline mr-1" />}
|
||||||
{day.doses.length} {day.doses.length === 1 ? t('dose') : t('doses')}
|
{day.doses.length} {day.doses.length === 1 ? t('dose') : t('doses')}
|
||||||
@@ -180,7 +180,7 @@ const DaySchedule: React.FC<DayScheduleProps> = ({
|
|||||||
>
|
>
|
||||||
<Badge
|
<Badge
|
||||||
variant="outline"
|
variant="outline"
|
||||||
className={`text-xs ${totalMgDiff > 0 ? 'bg-blue-100 dark:bg-blue-900/40 dark:text-blue-200' : 'bg-orange-100 dark:bg-orange-900/40 dark:text-orange-200'}`}
|
className={`text-xs ${totalMgDiff > 0 ? 'bg-blue-100 dark:bg-blue-900/60 dark:text-blue-200' : 'bg-orange-100 dark:bg-orange-900/60 dark:text-orange-200'}`}
|
||||||
>
|
>
|
||||||
{totalMgDiff > 0 ? <TrendingUp className="h-3 w-3 inline mr-1" /> : <TrendingDown className="h-3 w-3 inline mr-1" />}
|
{totalMgDiff > 0 ? <TrendingUp className="h-3 w-3 inline mr-1" /> : <TrendingDown className="h-3 w-3 inline mr-1" />}
|
||||||
{day.doses.reduce((sum, dose) => sum + (parseFloat(dose.ldx) || 0), 0).toFixed(1)} mg
|
{day.doses.reduce((sum, dose) => sum + (parseFloat(dose.ldx) || 0), 0).toFixed(1)} mg
|
||||||
@@ -291,7 +291,7 @@ const DaySchedule: React.FC<DayScheduleProps> = ({
|
|||||||
size="sm"
|
size="sm"
|
||||||
variant="outline"
|
variant="outline"
|
||||||
disabled={day.isTemplate && day.doses.length === 1}
|
disabled={day.isTemplate && day.doses.length === 1}
|
||||||
className="h-9 w-9 p-0 border-destructive text-destructive hover:bg-destructive hover:text-destructive-foreground disabled:border-muted"
|
className="h-9 w-9 p-0 text-destructive hover:bg-destructive hover:text-destructive-foreground disabled:border-muted"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -109,7 +109,7 @@ const DisclaimerModal: React.FC<DisclaimerModalProps> = ({
|
|||||||
<span className="text-2xl">⛔</span>
|
<span className="text-2xl">⛔</span>
|
||||||
{t('disclaimerModalScheduleII')}
|
{t('disclaimerModalScheduleII')}
|
||||||
</h3>
|
</h3>
|
||||||
<p className="text-sm text-red-800 dark:text-red-300">
|
<p className="text-sm error-text">
|
||||||
{t('disclaimerModalScheduleIIText')}
|
{t('disclaimerModalScheduleIIText')}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -772,8 +772,8 @@ const Settings = ({
|
|||||||
/>
|
/>
|
||||||
{isAdvancedExpanded && (
|
{isAdvancedExpanded && (
|
||||||
<CardContent className="space-y-4">
|
<CardContent className="space-y-4">
|
||||||
<div className="bg-yellow-50 dark:bg-yellow-900/20 border border-yellow-200 dark:border-yellow-800 rounded-md p-3 text-sm">
|
<div className="warning-bg-box rounded-md p-3 text-sm">
|
||||||
<p className="text-yellow-800 dark:text-yellow-200">{t('advancedSettingsWarning')}</p>
|
<p className="warning-text">{t('advancedSettingsWarning')}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Standard Volume of Distribution */}
|
{/* Standard Volume of Distribution */}
|
||||||
@@ -816,7 +816,7 @@ const Settings = ({
|
|||||||
</SelectContent>
|
</SelectContent>
|
||||||
</FormSelect>
|
</FormSelect>
|
||||||
{pkParams.advanced.standardVd?.preset === 'weight-based' && (
|
{pkParams.advanced.standardVd?.preset === 'weight-based' && (
|
||||||
<div className="ml-0 mt-2 p-2 bg-blue-50 dark:bg-blue-900/20 border border-blue-200 dark:border-blue-800 rounded text-xs text-blue-800 dark:text-blue-200">
|
<div className="ml-0 mt-2 p-2 info-bg-box rounded text-xs info-text">
|
||||||
ⓘ {t('weightBasedVdInfo')}
|
ⓘ {t('weightBasedVdInfo')}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -206,8 +206,8 @@ const FormNumericInput = React.forwardRef<HTMLInputElement, NumericInputProps>(
|
|||||||
size="icon"
|
size="icon"
|
||||||
className={cn(
|
className={cn(
|
||||||
"h-9 w-9 rounded-r-none border-r-0",
|
"h-9 w-9 rounded-r-none border-r-0",
|
||||||
hasError && "border-destructive",
|
hasError && "error-border",
|
||||||
hasWarning && !hasError && "border-yellow-500"
|
hasWarning && !hasError && "warning-border"
|
||||||
)}
|
)}
|
||||||
onClick={() => updateValue(-1)}
|
onClick={() => updateValue(-1)}
|
||||||
disabled={isAtMin}
|
disabled={isAtMin}
|
||||||
@@ -227,8 +227,8 @@ const FormNumericInput = React.forwardRef<HTMLInputElement, NumericInputProps>(
|
|||||||
inputWidth, "h-9 z-10",
|
inputWidth, "h-9 z-10",
|
||||||
"rounded-none",
|
"rounded-none",
|
||||||
getAlignmentClass(),
|
getAlignmentClass(),
|
||||||
hasError && "border-destructive focus-visible:ring-destructive",
|
hasError && "error-border focus-visible:ring-destructive",
|
||||||
hasWarning && !hasError && "border-yellow-500 focus-visible:ring-yellow-500"
|
hasWarning && !hasError && "warning-border focus-visible:ring-amber-500"
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
@@ -239,8 +239,8 @@ const FormNumericInput = React.forwardRef<HTMLInputElement, NumericInputProps>(
|
|||||||
className={cn(
|
className={cn(
|
||||||
"h-9 w-9",
|
"h-9 w-9",
|
||||||
showResetButton ? "rounded-l-none rounded-r-none border-x-0" : "rounded-l-none border-l-0",
|
showResetButton ? "rounded-l-none rounded-r-none border-x-0" : "rounded-l-none border-l-0",
|
||||||
hasError && "border-destructive",
|
hasError && "error-border",
|
||||||
hasWarning && !hasError && "border-yellow-500"
|
hasWarning && !hasError && "warning-border"
|
||||||
)}
|
)}
|
||||||
onClick={() => updateValue(1)}
|
onClick={() => updateValue(1)}
|
||||||
disabled={isAtMax}
|
disabled={isAtMax}
|
||||||
@@ -257,8 +257,8 @@ const FormNumericInput = React.forwardRef<HTMLInputElement, NumericInputProps>(
|
|||||||
size="icon"
|
size="icon"
|
||||||
className={cn(
|
className={cn(
|
||||||
"h-9 w-9 rounded-l-none",
|
"h-9 w-9 rounded-l-none",
|
||||||
hasError && "border-destructive",
|
hasError && "error-border",
|
||||||
hasWarning && !hasError && "border-yellow-500"
|
hasWarning && !hasError && "warning-border"
|
||||||
)}
|
)}
|
||||||
onClick={() => onChange(String(defaultValue ?? ''))}
|
onClick={() => onChange(String(defaultValue ?? ''))}
|
||||||
tabIndex={-1}
|
tabIndex={-1}
|
||||||
@@ -267,12 +267,12 @@ const FormNumericInput = React.forwardRef<HTMLInputElement, NumericInputProps>(
|
|||||||
</div>
|
</div>
|
||||||
{unit && <span className="text-sm text-muted-foreground whitespace-nowrap">{unit}</span>}
|
{unit && <span className="text-sm text-muted-foreground whitespace-nowrap">{unit}</span>}
|
||||||
{hasError && isFocused && errorMessage && (
|
{hasError && isFocused && errorMessage && (
|
||||||
<div className="absolute top-full left-0 mt-1 z-20 w-64 bg-destructive text-destructive-foreground text-xs p-2 rounded-md shadow-lg">
|
<div className="absolute top-full left-0 mt-1 z-20 w-80 error-bubble text-xs p-2 rounded-md shadow-lg">
|
||||||
{errorMessage}
|
{errorMessage}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{hasWarning && isFocused && warningMessage && (
|
{hasWarning && isFocused && warningMessage && (
|
||||||
<div className="absolute top-full left-0 mt-1 z-20 w-48 bg-yellow-500 text-white text-xs p-2 rounded-md shadow-lg">
|
<div className="absolute top-full left-0 mt-1 z-20 w-80 warning-bubble text-xs p-2 rounded-md shadow-lg">
|
||||||
{warningMessage}
|
{warningMessage}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -38,9 +38,10 @@ export const FormSelect: React.FC<FormSelectProps> = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex items-center gap-0">
|
<div className="flex items-center gap-0">
|
||||||
<Select value={value} onValueChange={onValueChange}>
|
<Select value={value} onValueChange={onValueChange}>
|
||||||
<SelectTrigger className={cn(
|
<SelectTrigger className={cn(
|
||||||
showResetButton && "rounded-r-none border-r-0 z-10",
|
showResetButton && "rounded-r-none border-r-0 z-10",
|
||||||
|
"bg-background",
|
||||||
triggerClassName
|
triggerClassName
|
||||||
)}>
|
)}>
|
||||||
<SelectValue placeholder={placeholder} />
|
<SelectValue placeholder={placeholder} />
|
||||||
|
|||||||
@@ -199,8 +199,8 @@ const FormTimeInput = React.forwardRef<HTMLInputElement, TimeInputProps>(
|
|||||||
"w-20 h-9 z-20",
|
"w-20 h-9 z-20",
|
||||||
"rounded-r-none",
|
"rounded-r-none",
|
||||||
getAlignmentClass(),
|
getAlignmentClass(),
|
||||||
hasError && "border-destructive focus-visible:ring-destructive",
|
hasError && "error-border focus-visible:ring-destructive",
|
||||||
hasWarning && !hasError && "border-yellow-500 focus-visible:ring-yellow-500"
|
hasWarning && !hasError && "warning-border focus-visible:ring-amber-500"
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
@@ -289,12 +289,12 @@ const FormTimeInput = React.forwardRef<HTMLInputElement, TimeInputProps>(
|
|||||||
</div>
|
</div>
|
||||||
{unit && <span className="text-sm text-muted-foreground whitespace-nowrap">{unit}</span>}
|
{unit && <span className="text-sm text-muted-foreground whitespace-nowrap">{unit}</span>}
|
||||||
{hasError && isFocused && errorMessage && (
|
{hasError && isFocused && errorMessage && (
|
||||||
<div className="absolute top-full left-0 mt-1 z-50 w-48 bg-destructive text-destructive-foreground text-xs p-2 rounded-md shadow-lg">
|
<div className="absolute top-full left-0 mt-1 z-50 w-80 error-bubble text-xs p-2 rounded-md shadow-lg">
|
||||||
{errorMessage}
|
{errorMessage}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{hasWarning && isFocused && warningMessage && (
|
{hasWarning && isFocused && warningMessage && (
|
||||||
<div className="absolute top-full left-0 mt-1 z-50 w-48 bg-yellow-500 text-white text-xs p-2 rounded-md shadow-lg">
|
<div className="absolute top-full left-0 mt-1 z-50 w-80 warning-bubble text-xs p-2 rounded-md shadow-lg">
|
||||||
{warningMessage}
|
{warningMessage}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -48,6 +48,10 @@
|
|||||||
--accent-foreground: 0 0% 90%;
|
--accent-foreground: 0 0% 90%;
|
||||||
--destructive: 0 84% 60%;
|
--destructive: 0 84% 60%;
|
||||||
--destructive-foreground: 0 0% 98%;
|
--destructive-foreground: 0 0% 98%;
|
||||||
|
--bubble-error: 0 84% 60%;
|
||||||
|
--bubble-error-foreground: 0 0% 98%;
|
||||||
|
--bubble-warning: 42 100% 60%;
|
||||||
|
--bubble-warning-foreground: 0 0% 98%;
|
||||||
--border: 0 0% 25%;
|
--border: 0 0% 25%;
|
||||||
--input: 0 0% 25%;
|
--input: 0 0% 25%;
|
||||||
--ring: 0 0% 40%;
|
--ring: 0 0% 40%;
|
||||||
@@ -68,3 +72,55 @@
|
|||||||
font-feature-settings: "rlig" 1, "calt" 1;
|
font-feature-settings: "rlig" 1, "calt" 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@layer components {
|
||||||
|
/* Error message bubble - validation popups */
|
||||||
|
.error-bubble {
|
||||||
|
@apply bg-[hsl(var(--background))] text-[hsl(var(--foreground))] border border-red-500 dark:border-red-500;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Warning message bubble - validation popups */
|
||||||
|
.warning-bubble {
|
||||||
|
@apply bg-[hsl(var(--background))] text-[hsl(var(--foreground))] border border-amber-500 dark:border-amber-500;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Error border - for input fields with errors */
|
||||||
|
.error-border {
|
||||||
|
@apply !border-red-500;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Warning border - for input fields with warnings */
|
||||||
|
.warning-border {
|
||||||
|
@apply !border-amber-500;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Error background box - for static error/warning sections */
|
||||||
|
.error-bg-box {
|
||||||
|
@apply bg-[hsl(var(--background))] border border-red-500 dark:border-red-500;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Warning background box - for static warning sections */
|
||||||
|
.warning-bg-box {
|
||||||
|
@apply bg-[hsl(var(--background))] border border-amber-500 dark:border-amber-500;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Info background box - for informational sections */
|
||||||
|
.info-bg-box {
|
||||||
|
@apply bg-[hsl(var(--background))] border border-blue-500 dark:border-blue-500;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Error text - for inline error text */
|
||||||
|
.error-text {
|
||||||
|
@apply text-[hsl(var(--foreground))];
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Warning text - for inline warning text */
|
||||||
|
.warning-text {
|
||||||
|
@apply text-[hsl(var(--foreground))];
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Info text - for inline info text */
|
||||||
|
.info-text {
|
||||||
|
@apply text-[hsl(var(--foreground))];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user