28 changed files with 362 additions and 311 deletions
@ -0,0 +1,9 @@ |
|||
module.exports = { |
|||
extends: ['@commitlint/config-conventional'], |
|||
rules: { |
|||
// Downgrade strict case rules to warnings (level 1) instead of errors (level 2)
|
|||
// This eliminates red error messages while maintaining helpful guidance
|
|||
'subject-case': [1, 'never', ['sentence-case', 'start-case', 'pascal-case', 'upper-case']], |
|||
'subject-full-stop': [1, 'never', '.'], |
|||
} |
|||
}; |
@ -0,0 +1,69 @@ |
|||
# Z-Index Guide — TimeSafari |
|||
|
|||
**Author**: Development Team |
|||
**Date**: 2025-08-25T19:38:09-08:00 |
|||
**Status**: 🎯 **ACTIVE** - Z-index layering standards |
|||
|
|||
## Objective |
|||
Establish consistent z-index values across the TimeSafari application to ensure proper layering of UI elements. |
|||
|
|||
## Result |
|||
This document defines the z-index hierarchy for all UI components. |
|||
|
|||
## Use/Run |
|||
Reference these values when implementing new components or modifying existing ones to maintain consistent layering. |
|||
|
|||
## Z-Index Hierarchy |
|||
|
|||
| Component | Z-Index | Usage | |
|||
|-----------|---------|-------| |
|||
| **Map** | `40` | Base map layer and map-related overlays | |
|||
| **QuickNav** | `50` | Quick navigation bottom bar | |
|||
| **Dialogs and Modals** | `100` | Modal dialogs, popups, and overlay content | |
|||
| **Notifications and Toasts** | `120` | System notifications, alerts, and toast messages | |
|||
|
|||
## Best Practices |
|||
|
|||
1. **Never exceed 120** - Keep the highest z-index reserved for critical notifications |
|||
2. **Use increments of 10** - Leave room for future additions between layers |
|||
3. **Document exceptions** - If you need a z-index outside this range, document the reason |
|||
4. **Test layering** - Verify z-index behavior across different screen sizes and devices |
|||
|
|||
## Common Pitfalls |
|||
|
|||
- **Avoid arbitrary values** - Don't use random z-index numbers |
|||
- **Don't nest high z-index** - Keep child elements within their parent's z-index range |
|||
- **Consider stacking context** - Remember that `position: relative` creates new stacking contexts |
|||
|
|||
## Next Steps |
|||
|
|||
| Owner | Task | Exit Criteria | Target Date | |
|||
|-------|------|---------------|-------------| |
|||
| Dev Team | Apply z-index classes to existing components | All components use defined z-index values | 2025-09-01 | |
|||
|
|||
## Competence Hooks |
|||
|
|||
- **Why this works**: Creates predictable layering hierarchy that prevents UI conflicts |
|||
- **Common pitfalls**: Using arbitrary z-index values or exceeding the defined range |
|||
- **Next skill unlock**: Learn about CSS stacking contexts and their impact on z-index |
|||
- **Teach-back**: Explain the z-index hierarchy to a team member without referencing this guide |
|||
|
|||
## Collaboration Hooks |
|||
|
|||
- **Reviewers**: Frontend team, UI/UX designers |
|||
- **Sign-off checklist**: |
|||
- [ ] All new components follow z-index guidelines |
|||
- [ ] Existing components updated to use defined values |
|||
- [ ] Cross-browser testing completed |
|||
- [ ] Mobile responsiveness verified |
|||
|
|||
## Assumptions & Limits |
|||
|
|||
- Assumes modern browser support for z-index |
|||
- Limited to 4 defined layers (expandable if needed) |
|||
- Requires team discipline to maintain consistency |
|||
|
|||
## References |
|||
|
|||
- [MDN Z-Index Documentation](https://developer.mozilla.org/en-US/docs/Web/CSS/z-index) |
|||
- [CSS Stacking Context Guide](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Positioning/Understanding_z_index/The_stacking_context) |
@ -0,0 +1,185 @@ |
|||
import { Capacitor } from "@capacitor/core"; |
|||
import { Clipboard } from "@capacitor/clipboard"; |
|||
import { useClipboard } from "@vueuse/core"; |
|||
import { logger } from "@/utils/logger"; |
|||
|
|||
/** |
|||
* Platform-agnostic clipboard service that handles both web and native platforms |
|||
* Provides reliable clipboard functionality across all platforms including iOS |
|||
*/ |
|||
export class ClipboardService { |
|||
private static instance: ClipboardService | null = null; |
|||
|
|||
/** |
|||
* Get singleton instance of ClipboardService |
|||
*/ |
|||
public static getInstance(): ClipboardService { |
|||
if (!ClipboardService.instance) { |
|||
ClipboardService.instance = new ClipboardService(); |
|||
} |
|||
return ClipboardService.instance; |
|||
} |
|||
|
|||
/** |
|||
* Copy text to clipboard with platform-specific handling |
|||
* |
|||
* @param text - The text to copy to clipboard |
|||
* @returns Promise that resolves when copy is complete |
|||
* @throws Error if copy operation fails |
|||
*/ |
|||
public async copyToClipboard(text: string): Promise<void> { |
|||
const platform = Capacitor.getPlatform(); |
|||
const isNative = Capacitor.isNativePlatform(); |
|||
|
|||
logger.debug("[ClipboardService] Copying to clipboard:", { |
|||
text: text.substring(0, 50) + (text.length > 50 ? "..." : ""), |
|||
platform, |
|||
isNative, |
|||
timestamp: new Date().toISOString(), |
|||
}); |
|||
|
|||
try { |
|||
if (isNative && (platform === "ios" || platform === "android")) { |
|||
// Use native Capacitor clipboard for mobile platforms
|
|||
await this.copyNative(text); |
|||
} else { |
|||
// Use web clipboard API for web/desktop platforms
|
|||
await this.copyWeb(text); |
|||
} |
|||
|
|||
logger.debug("[ClipboardService] Copy successful", { |
|||
platform, |
|||
timestamp: new Date().toISOString(), |
|||
}); |
|||
} catch (error) { |
|||
logger.error("[ClipboardService] Copy failed:", { |
|||
error: error instanceof Error ? error.message : String(error), |
|||
platform, |
|||
timestamp: new Date().toISOString(), |
|||
}); |
|||
throw error; |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* Copy text using native Capacitor clipboard API |
|||
* |
|||
* @param text - The text to copy |
|||
* @returns Promise that resolves when copy is complete |
|||
*/ |
|||
private async copyNative(text: string): Promise<void> { |
|||
try { |
|||
await Clipboard.write({ |
|||
string: text, |
|||
}); |
|||
} catch (error) { |
|||
logger.error("[ClipboardService] Native copy failed:", { |
|||
error: error instanceof Error ? error.message : String(error), |
|||
timestamp: new Date().toISOString(), |
|||
}); |
|||
throw new Error( |
|||
`Native clipboard copy failed: ${error instanceof Error ? error.message : String(error)}`, |
|||
); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* Copy text using web clipboard API with fallback |
|||
* |
|||
* @param text - The text to copy |
|||
* @returns Promise that resolves when copy is complete |
|||
*/ |
|||
private async copyWeb(text: string): Promise<void> { |
|||
try { |
|||
// Try VueUse clipboard first (handles some edge cases)
|
|||
const { copy } = useClipboard(); |
|||
await copy(text); |
|||
} catch (error) { |
|||
logger.warn( |
|||
"[ClipboardService] VueUse clipboard failed, trying native API:", |
|||
{ |
|||
error: error instanceof Error ? error.message : String(error), |
|||
timestamp: new Date().toISOString(), |
|||
}, |
|||
); |
|||
|
|||
// Fallback to native navigator.clipboard
|
|||
if (navigator.clipboard && navigator.clipboard.writeText) { |
|||
await navigator.clipboard.writeText(text); |
|||
} else { |
|||
throw new Error("Clipboard API not supported in this browser"); |
|||
} |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* Read text from clipboard (platform-specific) |
|||
* |
|||
* @returns Promise that resolves to the clipboard text |
|||
* @throws Error if read operation fails |
|||
*/ |
|||
public async readFromClipboard(): Promise<string> { |
|||
const platform = Capacitor.getPlatform(); |
|||
const isNative = Capacitor.isNativePlatform(); |
|||
|
|||
try { |
|||
if (isNative && (platform === "ios" || platform === "android")) { |
|||
// Use native Capacitor clipboard for mobile platforms
|
|||
const result = await Clipboard.read(); |
|||
return result.value || ""; |
|||
} else { |
|||
// Use web clipboard API for web/desktop platforms
|
|||
if (navigator.clipboard && navigator.clipboard.readText) { |
|||
return await navigator.clipboard.readText(); |
|||
} else { |
|||
throw new Error("Clipboard read API not supported in this browser"); |
|||
} |
|||
} |
|||
} catch (error) { |
|||
logger.error("[ClipboardService] Read from clipboard failed:", { |
|||
error: error instanceof Error ? error.message : String(error), |
|||
platform, |
|||
timestamp: new Date().toISOString(), |
|||
}); |
|||
throw error; |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* Check if clipboard is supported on current platform |
|||
* |
|||
* @returns boolean indicating if clipboard is supported |
|||
*/ |
|||
public isSupported(): boolean { |
|||
const platform = Capacitor.getPlatform(); |
|||
const isNative = Capacitor.isNativePlatform(); |
|||
|
|||
if (isNative && (platform === "ios" || platform === "android")) { |
|||
return true; // Capacitor clipboard should work on native platforms
|
|||
} |
|||
|
|||
// Check web clipboard support
|
|||
return !!(navigator.clipboard && navigator.clipboard.writeText); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* Convenience function to copy text to clipboard |
|||
* Uses the singleton ClipboardService instance |
|||
* |
|||
* @param text - The text to copy to clipboard |
|||
* @returns Promise that resolves when copy is complete |
|||
*/ |
|||
export async function copyToClipboard(text: string): Promise<void> { |
|||
return ClipboardService.getInstance().copyToClipboard(text); |
|||
} |
|||
|
|||
/** |
|||
* Convenience function to read text from clipboard |
|||
* Uses the singleton ClipboardService instance |
|||
* |
|||
* @returns Promise that resolves to the clipboard text |
|||
*/ |
|||
export async function readFromClipboard(): Promise<string> { |
|||
return ClipboardService.getInstance().readFromClipboard(); |
|||
} |
Loading…
Reference in new issue