starkon
Version:
Complete Next.js boilerplate with authentication, i18n & CLI - Create production-ready apps instantly
271 lines (241 loc) • 6.97 kB
text/typescript
import { twMerge } from 'tailwind-merge'
import { type ClassValue, clsx } from 'clsx'
export type Priority = 'LOW' | 'MEDIUM' | 'HIGH' | 'URGENT'
/**
* Combines class names with Tailwind CSS
*/
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs))
}
/**
* Debounces a function
*/
export function debounce<T extends (...args: any[]) => any>(fn: T, ms = 300): (...args: Parameters<T>) => void {
let timeoutId: ReturnType<typeof setTimeout>
return function (this: any, ...args: Parameters<T>) {
clearTimeout(timeoutId)
timeoutId = setTimeout(() => fn.apply(this, args), ms)
}
}
/**
* Checks if the client is in dark mode
*/
export function isDarkMode(): boolean {
if (typeof window === 'undefined') return false
return (
localStorage.getItem('theme') === 'dark' ||
(!localStorage.getItem('theme') && window.matchMedia('(prefers-color-scheme: dark)').matches)
)
}
/**
* Gets a nested object property by a dot-notation path
*/
export function get(obj: any, path: string, defaultValue: any = undefined) {
const travel = (regexp: RegExp) =>
String.prototype.split
.call(path, regexp)
.filter(Boolean)
.reduce((res, key) => (res !== null && res !== undefined ? res[key] : res), obj)
const result = travel(/[,[\]]+?/) || travel(/[,.[\]]+?/)
return result === undefined || result === obj ? defaultValue : result
}
/**
* Sanitizes HTML to prevent XSS attacks
*/
export function sanitizeHtml(html: string): string {
return html
.replace(/&/g, '&')
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/"/g, '"')
.replace(/'/g, ''')
}
/**
* Formats a date for display
*/
export function formatDate(date: Date | string, locale: string = 'en-US'): string {
const d = typeof date === 'string' ? new Date(date) : date
return d.toLocaleDateString(locale, {
year: 'numeric',
month: 'long',
day: 'numeric',
})
}
/**
* Type-safe LocalStorage access
*/
export const storage = {
get: <T>(key: string, defaultValue: T): T => {
if (typeof window === 'undefined') return defaultValue
try {
const item = window.localStorage.getItem(key)
return item ? (JSON.parse(item) as T) : defaultValue
} catch (error) {
console.error('Error getting item from localStorage', error)
return defaultValue
}
},
set: <T>(key: string, value: T): void => {
if (typeof window === 'undefined') return
try {
window.localStorage.setItem(key, JSON.stringify(value))
} catch (error) {
console.error('Error setting item in localStorage', error)
}
},
remove: (key: string): void => {
if (typeof window === 'undefined') return
try {
window.localStorage.removeItem(key)
} catch (error) {
console.error('Error removing item from localStorage', error)
}
},
}
export function formatRelativeTime(date: string | Date): string {
const now = new Date()
const targetDate = typeof date === 'string' ? new Date(date) : date
const diffInMs = now.getTime() - targetDate.getTime()
const diffInHours = Math.floor(diffInMs / (1000 * 60 * 60))
const diffInDays = Math.floor(diffInHours / 24)
if (diffInHours < 1) return 'Az önce'
if (diffInHours < 24) return `${diffInHours} saat önce`
if (diffInDays < 7) return `${diffInDays} gün önce`
if (diffInDays < 30) return `${Math.floor(diffInDays / 7)} hafta önce`
return formatDate(targetDate)
}
export function getPriorityColor(priority: Priority): string {
const colors = {
LOW: 'text-green-600 bg-green-50 border-green-200',
MEDIUM: 'text-yellow-600 bg-yellow-50 border-yellow-200',
HIGH: 'text-orange-600 bg-orange-50 border-orange-200',
URGENT: 'text-red-600 bg-red-50 border-red-200',
}
return colors[priority]
}
export function getPriorityText(priority: Priority): string {
const texts = {
LOW: 'Düşük',
MEDIUM: 'Orta',
HIGH: 'Yüksek',
URGENT: 'Acil',
}
return texts[priority]
}
export function generateId(): string {
return Math.random().toString(36).substring(2) + Date.now().toString(36)
}
export const validateJson = (jsonString: string): boolean => {
try {
JSON.parse(jsonString)
return true
} catch {
return false
}
}
// URL slug oluşturma
export function slugify(text: string): string {
return text
.toLowerCase()
.replace(/[^\w ]+/g, '')
.replace(/ +/g, '-')
}
// Array'den unique değerler
export function unique<T>(array: T[]): T[] {
return [...new Set(array)]
}
// Deep clone objesi
export function deepClone<T>(obj: T): T {
if (typeof structuredClone === 'function') {
return structuredClone(obj)
}
return JSON.parse(JSON.stringify(obj))
}
// Local storage güvenli kullanımı
export function safeLocalStorage() {
if (typeof window === 'undefined') {
return {
getItem: () => null,
setItem: () => {},
removeItem: () => {},
}
}
return {
getItem: (key: string) => {
try {
return localStorage.getItem(key)
} catch {
return null
}
},
setItem: (key: string, value: string) => {
try {
localStorage.setItem(key, value)
} catch {
// Silent fail
}
},
removeItem: (key: string) => {
try {
localStorage.removeItem(key)
} catch {
// Silent fail
}
},
}
}
// Validation helpers
export function isValidEmail(email: string): boolean {
const regex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/
return regex.test(email)
}
export function isValidUrl(url: string): boolean {
try {
new URL(url)
return true
} catch {
return false
}
}
// Array manipulation
export function moveArrayItem<T>(array: T[], fromIndex: number, toIndex: number): T[] {
const result = [...array]
const [removed] = result.splice(fromIndex, 1)
result.splice(toIndex, 0, removed)
return result
}
// Text truncation
export function truncate(text: string, length: number = 100): string {
if (text.length <= length) return text
return text.substring(0, length) + '...'
}
// File size formatting
export function formatFileSize(bytes: number): string {
if (bytes === 0) return '0 Bytes'
const k = 1024
const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB']
const i = Math.floor(Math.log(bytes) / Math.log(k))
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]
}
// Color utilities
export function hexToRgb(hex: string): { r: number; g: number; b: number } | null {
const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex)
return result
? {
r: parseInt(result[1], 16),
g: parseInt(result[2], 16),
b: parseInt(result[3], 16),
}
: null
}
export function rgbToHex(r: number, g: number, b: number): string {
return (
'#' +
[r, g, b]
.map((x) => {
const hex = x.toString(16)
return hex.length === 1 ? '0' + hex : hex
})
.join('')
)
}