@parto-system-design/ui 1.1.17 → 1.1.19
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +2 -2
- package/README.md +1 -1
- package/dist/components/brand/parto-logo.cjs +0 -2
- package/dist/components/brand/parto-logo.js +0 -2
- package/dist/components/charts/PartoAreaChart.cjs +2 -4
- package/dist/components/charts/PartoAreaChart.d.cts +1 -1
- package/dist/components/charts/PartoAreaChart.d.ts +1 -1
- package/dist/components/charts/PartoAreaChart.js +2 -4
- package/dist/components/charts/PartoBarChart.cjs +2 -4
- package/dist/components/charts/PartoBarChart.d.cts +1 -1
- package/dist/components/charts/PartoBarChart.d.ts +1 -1
- package/dist/components/charts/PartoBarChart.js +2 -4
- package/dist/components/charts/PartoLineChart.cjs +2 -4
- package/dist/components/charts/PartoLineChart.d.cts +1 -1
- package/dist/components/charts/PartoLineChart.d.ts +1 -1
- package/dist/components/charts/PartoLineChart.js +2 -4
- package/dist/components/charts/PartoPieChart.cjs +2 -4
- package/dist/components/charts/PartoPieChart.d.cts +1 -1
- package/dist/components/charts/PartoPieChart.d.ts +1 -1
- package/dist/components/charts/PartoPieChart.js +2 -4
- package/dist/components/ui/accordion.cjs +0 -2
- package/dist/components/ui/accordion.d.cts +4 -5
- package/dist/components/ui/accordion.d.ts +4 -5
- package/dist/components/ui/accordion.js +0 -2
- package/dist/components/ui/alert-dialog.cjs +0 -2
- package/dist/components/ui/alert-dialog.d.cts +9 -10
- package/dist/components/ui/alert-dialog.d.ts +9 -10
- package/dist/components/ui/alert-dialog.js +1 -3
- package/dist/components/ui/alert-rule-card.cjs +149 -12
- package/dist/components/ui/alert-rule-card.d.cts +2 -2
- package/dist/components/ui/alert-rule-card.d.ts +2 -2
- package/dist/components/ui/alert-rule-card.js +149 -12
- package/dist/components/ui/alert.cjs +3 -5
- package/dist/components/ui/alert.js +3 -5
- package/dist/components/ui/app-bar.cjs +0 -2
- package/dist/components/ui/app-bar.js +0 -2
- package/dist/components/ui/avatar.cjs +0 -2
- package/dist/components/ui/avatar.d.cts +3 -4
- package/dist/components/ui/avatar.d.ts +3 -4
- package/dist/components/ui/avatar.js +0 -2
- package/dist/components/ui/badge.cjs +0 -2
- package/dist/components/ui/badge.d.cts +1 -1
- package/dist/components/ui/badge.d.ts +1 -1
- package/dist/components/ui/badge.js +0 -2
- package/dist/components/ui/breadcrumb.cjs +0 -2
- package/dist/components/ui/breadcrumb.d.cts +7 -8
- package/dist/components/ui/breadcrumb.d.ts +7 -8
- package/dist/components/ui/breadcrumb.js +0 -2
- package/dist/components/ui/button.cjs +0 -2
- package/dist/components/ui/button.d.cts +2 -2
- package/dist/components/ui/button.d.ts +2 -2
- package/dist/components/ui/button.js +1 -3
- package/dist/components/ui/calendar.cjs +0 -2
- package/dist/components/ui/calendar.d.cts +1 -2
- package/dist/components/ui/calendar.d.ts +1 -2
- package/dist/components/ui/calendar.js +1 -3
- package/dist/components/ui/card.cjs +0 -2
- package/dist/components/ui/card.js +0 -2
- package/dist/components/ui/checkbox.cjs +0 -2
- package/dist/components/ui/checkbox.js +0 -2
- package/dist/components/ui/concept-card.cjs +179 -61
- package/dist/components/ui/concept-card.d.cts +3 -3
- package/dist/components/ui/concept-card.d.ts +3 -3
- package/dist/components/ui/concept-card.js +179 -61
- package/dist/components/ui/data-table.cjs +129 -3
- package/dist/components/ui/data-table.d.cts +1 -1
- package/dist/components/ui/data-table.d.ts +1 -1
- package/dist/components/ui/data-table.js +130 -4
- package/dist/components/ui/dialog.cjs +0 -2
- package/dist/components/ui/dialog.d.cts +2 -3
- package/dist/components/ui/dialog.d.ts +2 -3
- package/dist/components/ui/dialog.js +0 -2
- package/dist/components/ui/dropdown-menu.cjs +0 -2
- package/dist/components/ui/dropdown-menu.d.cts +15 -16
- package/dist/components/ui/dropdown-menu.d.ts +15 -16
- package/dist/components/ui/dropdown-menu.js +0 -2
- package/dist/components/ui/filter-provider.cjs +0 -2
- package/dist/components/ui/filter-provider.d.cts +1 -2
- package/dist/components/ui/filter-provider.d.ts +1 -2
- package/dist/components/ui/filter-provider.js +0 -2
- package/dist/components/ui/form.cjs +0 -2
- package/dist/components/ui/form.d.cts +7 -8
- package/dist/components/ui/form.d.ts +7 -8
- package/dist/components/ui/form.js +0 -2
- package/dist/components/ui/input.cjs +0 -2
- package/dist/components/ui/input.js +0 -2
- package/dist/components/ui/iran-province-heat.cjs +1 -3
- package/dist/components/ui/iran-province-heat.d.cts +1 -1
- package/dist/components/ui/iran-province-heat.d.ts +1 -1
- package/dist/components/ui/iran-province-heat.js +1 -3
- package/dist/components/ui/label.cjs +0 -2
- package/dist/components/ui/label.d.cts +1 -2
- package/dist/components/ui/label.d.ts +1 -2
- package/dist/components/ui/label.js +0 -2
- package/dist/components/ui/page-card.cjs +170 -33
- package/dist/components/ui/page-card.d.cts +3 -3
- package/dist/components/ui/page-card.d.ts +3 -3
- package/dist/components/ui/page-card.js +170 -33
- package/dist/components/ui/page-header.cjs +128 -2
- package/dist/components/ui/page-header.d.cts +1 -1
- package/dist/components/ui/page-header.d.ts +1 -1
- package/dist/components/ui/page-header.js +128 -2
- package/dist/components/ui/password-input.cjs +0 -2
- package/dist/components/ui/password-input.js +0 -2
- package/dist/components/ui/popover.cjs +0 -2
- package/dist/components/ui/popover.js +0 -2
- package/dist/components/ui/progress.cjs +1 -3
- package/dist/components/ui/progress.js +1 -3
- package/dist/components/ui/radio-card.cjs +0 -2
- package/dist/components/ui/radio-card.js +0 -2
- package/dist/components/ui/radio-group.cjs +0 -2
- package/dist/components/ui/radio-group.js +0 -2
- package/dist/components/ui/saved-query-card.cjs +0 -2
- package/dist/components/ui/saved-query-card.d.cts +1 -1
- package/dist/components/ui/saved-query-card.d.ts +1 -1
- package/dist/components/ui/saved-query-card.js +1 -3
- package/dist/components/ui/scroll-area.cjs +0 -2
- package/dist/components/ui/scroll-area.d.cts +2 -3
- package/dist/components/ui/scroll-area.d.ts +2 -3
- package/dist/components/ui/scroll-area.js +0 -2
- package/dist/components/ui/select.cjs +0 -2
- package/dist/components/ui/select.d.cts +1 -2
- package/dist/components/ui/select.d.ts +1 -2
- package/dist/components/ui/select.js +0 -2
- package/dist/components/ui/separator.cjs +0 -2
- package/dist/components/ui/separator.js +0 -2
- package/dist/components/ui/sheet.cjs +0 -2
- package/dist/components/ui/sheet.d.cts +9 -10
- package/dist/components/ui/sheet.d.ts +9 -10
- package/dist/components/ui/sheet.js +0 -2
- package/dist/components/ui/skeleton.cjs +0 -2
- package/dist/components/ui/skeleton.d.cts +8 -9
- package/dist/components/ui/skeleton.d.ts +8 -9
- package/dist/components/ui/skeleton.js +0 -2
- package/dist/components/ui/slider.cjs +0 -2
- package/dist/components/ui/slider.d.cts +1 -2
- package/dist/components/ui/slider.d.ts +1 -2
- package/dist/components/ui/slider.js +0 -2
- package/dist/components/ui/social-platform-badge.cjs +0 -2
- package/dist/components/ui/social-platform-badge.js +1 -3
- package/dist/components/ui/sonner.cjs +4 -6
- package/dist/components/ui/sonner.d.cts +2 -2
- package/dist/components/ui/sonner.d.ts +2 -2
- package/dist/components/ui/sonner.js +4 -6
- package/dist/components/ui/sparkline.cjs +3 -5
- package/dist/components/ui/sparkline.d.cts +1 -1
- package/dist/components/ui/sparkline.d.ts +1 -1
- package/dist/components/ui/sparkline.js +3 -5
- package/dist/components/ui/switch.cjs +0 -2
- package/dist/components/ui/switch.d.cts +1 -1
- package/dist/components/ui/switch.d.ts +1 -1
- package/dist/components/ui/switch.js +0 -2
- package/dist/components/ui/table.cjs +0 -2
- package/dist/components/ui/table.d.cts +9 -10
- package/dist/components/ui/table.d.ts +9 -10
- package/dist/components/ui/table.js +0 -2
- package/dist/components/ui/tabs.cjs +0 -2
- package/dist/components/ui/tabs.js +0 -2
- package/dist/components/ui/textarea.cjs +0 -2
- package/dist/components/ui/textarea.js +0 -2
- package/dist/components/ui/toggle-group.cjs +0 -2
- package/dist/components/ui/toggle-group.d.cts +1 -2
- package/dist/components/ui/toggle-group.d.ts +1 -2
- package/dist/components/ui/toggle-group.js +0 -2
- package/dist/components/ui/tooltip.cjs +0 -2
- package/dist/components/ui/tooltip.js +0 -2
- package/dist/{concept-card-BoJ5gIJD.d.ts → concept-card-BU8JL-gj.d.cts} +5 -5
- package/dist/{concept-card-BXra9mr0.d.cts → concept-card-D7PfDkNR.d.ts} +5 -5
- package/dist/hooks/use-hotkey-registry.cjs +0 -2
- package/dist/hooks/use-hotkey-registry.d.cts +1 -2
- package/dist/hooks/use-hotkey-registry.d.ts +1 -2
- package/dist/hooks/use-hotkey-registry.js +0 -2
- package/dist/hooks/use-hotkeys.cjs +0 -2
- package/dist/hooks/use-hotkeys.js +0 -2
- package/dist/{i18n-BfRhV5aw.d.cts → i18n-DD3DMY8O.d.cts} +94 -5
- package/dist/{i18n-ewyqbKM-.d.ts → i18n-UEClNsBy.d.ts} +94 -5
- package/dist/index.cjs +1244 -1173
- package/dist/index.css +8010 -5459
- package/dist/index.d.cts +213 -992
- package/dist/index.d.ts +213 -992
- package/dist/index.js +1210 -1153
- package/dist/{page-card-C9XXXOVr.d.cts → page-card-DjztZrFM.d.cts} +4 -4
- package/dist/{page-card-DAnbez_f.d.ts → page-card-sIE4lvnb.d.ts} +4 -4
- package/dist/server-FTUA8opZ.d.cts +829 -0
- package/dist/server-m6tiB6DB.d.ts +829 -0
- package/dist/server.cjs +1565 -0
- package/dist/server.d.cts +8 -0
- package/dist/server.d.ts +8 -0
- package/dist/server.js +1483 -0
- package/dist/theme.css +50 -0
- package/dist/{toggle-group-B8r4LOQw.d.cts → toggle-group-Bis6suuR.d.cts} +3 -4
- package/dist/{toggle-group-B8r4LOQw.d.ts → toggle-group-Bis6suuR.d.ts} +3 -4
- package/dist/{utils-DlXWmDZ-.d.cts → utils-Czyp5Ned.d.cts} +1 -1
- package/dist/{utils-DlXWmDZ-.d.ts → utils-Czyp5Ned.d.ts} +1 -1
- package/package.json +455 -187
- package/tailwind.config.ts +184 -183
- package/dist/components/brand/parto-logo.cjs.map +0 -1
- package/dist/components/brand/parto-logo.js.map +0 -1
- package/dist/components/charts/PartoAreaChart.cjs.map +0 -1
- package/dist/components/charts/PartoAreaChart.js.map +0 -1
- package/dist/components/charts/PartoBarChart.cjs.map +0 -1
- package/dist/components/charts/PartoBarChart.js.map +0 -1
- package/dist/components/charts/PartoLineChart.cjs.map +0 -1
- package/dist/components/charts/PartoLineChart.js.map +0 -1
- package/dist/components/charts/PartoPieChart.cjs.map +0 -1
- package/dist/components/charts/PartoPieChart.js.map +0 -1
- package/dist/components/ui/accordion.cjs.map +0 -1
- package/dist/components/ui/accordion.js.map +0 -1
- package/dist/components/ui/alert-dialog.cjs.map +0 -1
- package/dist/components/ui/alert-dialog.js.map +0 -1
- package/dist/components/ui/alert-rule-card.cjs.map +0 -1
- package/dist/components/ui/alert-rule-card.js.map +0 -1
- package/dist/components/ui/alert.cjs.map +0 -1
- package/dist/components/ui/alert.js.map +0 -1
- package/dist/components/ui/app-bar.cjs.map +0 -1
- package/dist/components/ui/app-bar.js.map +0 -1
- package/dist/components/ui/avatar.cjs.map +0 -1
- package/dist/components/ui/avatar.js.map +0 -1
- package/dist/components/ui/badge.cjs.map +0 -1
- package/dist/components/ui/badge.js.map +0 -1
- package/dist/components/ui/breadcrumb.cjs.map +0 -1
- package/dist/components/ui/breadcrumb.js.map +0 -1
- package/dist/components/ui/button.cjs.map +0 -1
- package/dist/components/ui/button.js.map +0 -1
- package/dist/components/ui/calendar.cjs.map +0 -1
- package/dist/components/ui/calendar.js.map +0 -1
- package/dist/components/ui/card.cjs.map +0 -1
- package/dist/components/ui/card.js.map +0 -1
- package/dist/components/ui/checkbox.cjs.map +0 -1
- package/dist/components/ui/checkbox.js.map +0 -1
- package/dist/components/ui/concept-card.cjs.map +0 -1
- package/dist/components/ui/concept-card.js.map +0 -1
- package/dist/components/ui/data-table.cjs.map +0 -1
- package/dist/components/ui/data-table.js.map +0 -1
- package/dist/components/ui/dialog.cjs.map +0 -1
- package/dist/components/ui/dialog.js.map +0 -1
- package/dist/components/ui/dropdown-menu.cjs.map +0 -1
- package/dist/components/ui/dropdown-menu.js.map +0 -1
- package/dist/components/ui/filter-provider.cjs.map +0 -1
- package/dist/components/ui/filter-provider.js.map +0 -1
- package/dist/components/ui/form.cjs.map +0 -1
- package/dist/components/ui/form.js.map +0 -1
- package/dist/components/ui/input.cjs.map +0 -1
- package/dist/components/ui/input.js.map +0 -1
- package/dist/components/ui/iran-province-heat.cjs.map +0 -1
- package/dist/components/ui/iran-province-heat.js.map +0 -1
- package/dist/components/ui/label.cjs.map +0 -1
- package/dist/components/ui/label.js.map +0 -1
- package/dist/components/ui/page-card.cjs.map +0 -1
- package/dist/components/ui/page-card.js.map +0 -1
- package/dist/components/ui/page-header.cjs.map +0 -1
- package/dist/components/ui/page-header.js.map +0 -1
- package/dist/components/ui/password-input.cjs.map +0 -1
- package/dist/components/ui/password-input.js.map +0 -1
- package/dist/components/ui/popover.cjs.map +0 -1
- package/dist/components/ui/popover.js.map +0 -1
- package/dist/components/ui/progress.cjs.map +0 -1
- package/dist/components/ui/progress.js.map +0 -1
- package/dist/components/ui/radio-card.cjs.map +0 -1
- package/dist/components/ui/radio-card.js.map +0 -1
- package/dist/components/ui/radio-group.cjs.map +0 -1
- package/dist/components/ui/radio-group.js.map +0 -1
- package/dist/components/ui/saved-query-card.cjs.map +0 -1
- package/dist/components/ui/saved-query-card.js.map +0 -1
- package/dist/components/ui/scroll-area.cjs.map +0 -1
- package/dist/components/ui/scroll-area.js.map +0 -1
- package/dist/components/ui/select.cjs.map +0 -1
- package/dist/components/ui/select.js.map +0 -1
- package/dist/components/ui/separator.cjs.map +0 -1
- package/dist/components/ui/separator.js.map +0 -1
- package/dist/components/ui/sheet.cjs.map +0 -1
- package/dist/components/ui/sheet.js.map +0 -1
- package/dist/components/ui/skeleton.cjs.map +0 -1
- package/dist/components/ui/skeleton.js.map +0 -1
- package/dist/components/ui/slider.cjs.map +0 -1
- package/dist/components/ui/slider.js.map +0 -1
- package/dist/components/ui/social-platform-badge.cjs.map +0 -1
- package/dist/components/ui/social-platform-badge.js.map +0 -1
- package/dist/components/ui/sonner.cjs.map +0 -1
- package/dist/components/ui/sonner.js.map +0 -1
- package/dist/components/ui/sparkline.cjs.map +0 -1
- package/dist/components/ui/sparkline.js.map +0 -1
- package/dist/components/ui/switch.cjs.map +0 -1
- package/dist/components/ui/switch.js.map +0 -1
- package/dist/components/ui/table.cjs.map +0 -1
- package/dist/components/ui/table.js.map +0 -1
- package/dist/components/ui/tabs.cjs.map +0 -1
- package/dist/components/ui/tabs.js.map +0 -1
- package/dist/components/ui/textarea.cjs.map +0 -1
- package/dist/components/ui/textarea.js.map +0 -1
- package/dist/components/ui/toggle-group.cjs.map +0 -1
- package/dist/components/ui/toggle-group.js.map +0 -1
- package/dist/components/ui/tooltip.cjs.map +0 -1
- package/dist/components/ui/tooltip.js.map +0 -1
- package/dist/hooks/use-hotkey-registry.cjs.map +0 -1
- package/dist/hooks/use-hotkey-registry.js.map +0 -1
- package/dist/hooks/use-hotkeys.cjs.map +0 -1
- package/dist/hooks/use-hotkeys.js.map +0 -1
- package/dist/index.cjs.map +0 -1
- package/dist/index.js.map +0 -1
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/lib/utils.ts","../../../src/components/ui/skeleton.tsx"],"names":[],"mappings":"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","file":"skeleton.js","sourcesContent":["import { type ClassValue, clsx } from 'clsx'\nimport { twMerge } from 'tailwind-merge'\nimport { formatJalaliDate } from '@/lib/jalali-utils'\n\nexport function cn(...inputs: ClassValue[]) {\n return twMerge(clsx(inputs))\n}\n\nexport type SupportedLocale = 'fa' | 'ar' | 'en'\n\n/**\n * Convert digits in a string to Persian/Arabic numerals based on locale.\n * @example convertToLocalNumbers('123', 'fa') => '۱۲۳'\n * @example convertToLocalNumbers('123', 'en') => '123'\n */\nexport function convertToLocalNumbers(text: string | number, locale: SupportedLocale): string {\n if (locale === 'fa' || locale === 'ar') {\n const persianDigits = ['۰', '۱', '۲', '۳', '۴', '۵', '۶', '۷', '۸', '۹']\n return String(text).replace(/\\d/g, (digit) => persianDigits[parseInt(digit)])\n }\n return String(text)\n}\n\n/**\n * Format large numbers with locale-aware suffixes (K/M/B).\n * @example formatLargeNumber(1500, 'fa') => '۱.۵ هزار'\n * @example formatLargeNumber(1500, 'en') => '1.5K'\n */\nexport function formatLargeNumber(num: number, locale: SupportedLocale): string {\n if (num >= 1_000_000_000) {\n const formatted = (num / 1_000_000_000).toFixed(1).replace(/\\.0$/, '')\n return convertToLocalNumbers(formatted, locale) + (locale === 'en' ? 'B' : ' میلیارد')\n }\n if (num >= 1_000_000) {\n const formatted = (num / 1_000_000).toFixed(1).replace(/\\.0$/, '')\n return convertToLocalNumbers(formatted, locale) + (locale === 'en' ? 'M' : ' میلیون')\n }\n if (num >= 1_000) {\n const formatted = (num / 1_000).toFixed(1).replace(/\\.0$/, '')\n return convertToLocalNumbers(formatted, locale) + (locale === 'en' ? 'K' : ' هزار')\n }\n return convertToLocalNumbers(num.toString().replace(/\\B(?=(\\d{3})+(?!\\d))/g, ','), locale)\n}\n\n/**\n * Format number to Instagram-style short format (English only).\n * @example formatNumber(123456, 'short') => '123K'\n * @example formatNumber(123456, 'exact') => '123,456'\n */\nexport function formatNumber(num: number | undefined, format: 'exact' | 'short' = 'exact'): string {\n if (num === undefined || num === null) return '0'\n\n if (format === 'exact') {\n return num.toLocaleString('en-US')\n }\n\n // Short format (Instagram style)\n if (num >= 1_000_000_000) {\n return `${(num / 1_000_000_000).toFixed(1).replace(/\\.0$/, '')}B`\n }\n if (num >= 1_000_000) {\n return `${(num / 1_000_000).toFixed(1).replace(/\\.0$/, '')}M`\n }\n if (num >= 1_000) {\n return `${(num / 1_000).toFixed(1).replace(/\\.0$/, '')}K`\n }\n return num.toString()\n}\n\n/**\n * Format date to relative time with absolute on hover (Persian)\n * @example formatRelativeTime(new Date()) => '۲ ساعت پیش'\n */\nexport function formatRelativeTime(date: Date | string | number): string {\n const now = new Date()\n const then = new Date(date)\n const diffInSeconds = Math.floor((now.getTime() - then.getTime()) / 1000)\n\n if (diffInSeconds < 60) {\n return 'همین الان'\n }\n\n const diffInMinutes = Math.floor(diffInSeconds / 60)\n if (diffInMinutes < 60) {\n return `${convertToLocalNumbers(diffInMinutes, 'fa')} دقیقه پیش`\n }\n\n const diffInHours = Math.floor(diffInMinutes / 60)\n if (diffInHours < 24) {\n return `${convertToLocalNumbers(diffInHours, 'fa')} ساعت پیش`\n }\n\n const diffInDays = Math.floor(diffInHours / 24)\n if (diffInDays < 7) {\n return `${convertToLocalNumbers(diffInDays, 'fa')} روز پیش`\n }\n\n const diffInWeeks = Math.floor(diffInDays / 7)\n if (diffInWeeks < 4) {\n return `${convertToLocalNumbers(diffInWeeks, 'fa')} هفته پیش`\n }\n\n const diffInMonths = Math.floor(diffInDays / 30)\n if (diffInMonths < 12) {\n return `${convertToLocalNumbers(diffInMonths, 'fa')} ماه پیش`\n }\n\n const diffInYears = Math.floor(diffInDays / 365)\n return `${convertToLocalNumbers(diffInYears, 'fa')} سال پیش`\n}\n\n/**\n * Format date to absolute format (Persian / Jalali)\n * Uses date-fns-jalali for accurate Jalali conversion.\n * @example formatAbsoluteTime(new Date()) => '۱۵ دی ۱۴۰۳، ۱۵:۳۰'\n */\nexport function formatAbsoluteTime(date: Date | string | number): string {\n const d = new Date(date)\n return formatJalaliDate(d, 'd MMMM yyyy، HH:mm')\n}\n","'use client'\n\nimport * as React from 'react'\nimport { cva, type VariantProps } from 'class-variance-authority'\nimport { cn } from '@/lib/utils'\n\nconst skeletonVariants = cva('bg-accent animate-pulse', {\n variants: {\n shape: {\n rect: 'rounded-md',\n line: 'rounded-full h-4 w-full',\n circle: 'rounded-full',\n text: 'rounded h-4',\n },\n size: {\n sm: '',\n md: '',\n lg: '',\n },\n },\n compoundVariants: [\n { shape: 'circle', size: 'sm', class: 'size-8' },\n { shape: 'circle', size: 'md', class: 'size-10' },\n { shape: 'circle', size: 'lg', class: 'size-14' },\n ],\n defaultVariants: {\n shape: 'rect',\n size: 'md',\n },\n})\n\nexport interface SkeletonProps extends React.HTMLAttributes<HTMLDivElement>, VariantProps<typeof skeletonVariants> {\n /**\n * تعداد عناصر skeleton برای نمایش (برای shape های line و text)\n * Number of skeleton elements to repeat (for line/text shapes)\n */\n count?: number\n}\n\nfunction Skeleton({ className, shape, size, count, ...props }: SkeletonProps) {\n if (count && count > 1) {\n return (\n <div role=\"status\" aria-busy=\"true\" aria-label=\"Loading\" className=\"space-y-2\">\n {Array.from({ length: count }).map((_, i) => (\n <div\n key={i}\n data-slot=\"skeleton\"\n aria-hidden=\"true\"\n className={cn(skeletonVariants({ shape, size }), className)}\n />\n ))}\n </div>\n )\n }\n\n return (\n <div\n data-slot=\"skeleton\"\n role=\"status\"\n aria-busy=\"true\"\n aria-label=\"Loading\"\n {...props}\n className={cn(skeletonVariants({ shape, size }), className)}\n />\n )\n}\n\nSkeleton.displayName = 'Skeleton'\n\n/* -------------------------------------------------------------------------- */\n/* Named Skeleton Presets */\n/* -------------------------------------------------------------------------- */\n\nexport interface MetricCardSkeletonProps {\n className?: string\n}\n\nfunction MetricCardSkeleton({ className }: MetricCardSkeletonProps) {\n return (\n <div\n data-slot=\"metric-card-skeleton\"\n role=\"status\"\n aria-busy=\"true\"\n aria-label=\"Loading metric\"\n className={cn('rounded-lg border border-border bg-card p-4 space-y-3', className)}\n >\n <Skeleton shape=\"line\" className=\"h-3 w-1/3\" aria-hidden=\"true\" />\n <Skeleton shape=\"rect\" className=\"h-8 w-1/2\" aria-hidden=\"true\" />\n <Skeleton shape=\"line\" className=\"h-3 w-1/4\" aria-hidden=\"true\" />\n </div>\n )\n}\nMetricCardSkeleton.displayName = 'MetricCardSkeleton'\n\nexport interface ChartSkeletonProps {\n className?: string\n}\n\nfunction ChartSkeleton({ className }: ChartSkeletonProps) {\n return (\n <div\n data-slot=\"chart-skeleton\"\n role=\"status\"\n aria-busy=\"true\"\n aria-label=\"Loading chart\"\n className={cn('w-full rounded-md overflow-hidden', className)}\n >\n <Skeleton shape=\"rect\" className=\"w-full h-full min-h-[200px]\" aria-hidden=\"true\" />\n </div>\n )\n}\nChartSkeleton.displayName = 'ChartSkeleton'\n\nexport interface TableSkeletonProps {\n rows?: number\n className?: string\n}\n\nfunction TableSkeleton({ rows = 5, className }: TableSkeletonProps) {\n return (\n <div\n data-slot=\"table-skeleton\"\n role=\"status\"\n aria-busy=\"true\"\n aria-label=\"Loading table\"\n className={cn('w-full rounded-md border border-border overflow-hidden', className)}\n >\n <div className=\"flex gap-4 bg-muted/50 px-4 py-3 border-b border-border\" aria-hidden=\"true\">\n <Skeleton shape=\"line\" className=\"h-4 flex-1\" />\n <Skeleton shape=\"line\" className=\"h-4 flex-1\" />\n <Skeleton shape=\"line\" className=\"h-4 flex-1\" />\n <Skeleton shape=\"line\" className=\"h-4 w-16\" />\n </div>\n {Array.from({ length: rows }).map((_, i) => (\n <div\n key={i}\n aria-hidden=\"true\"\n className={cn('flex gap-4 px-4 py-3', i !== rows - 1 && 'border-b border-border')}\n >\n <Skeleton shape=\"line\" className=\"h-4 flex-1\" />\n <Skeleton shape=\"line\" className=\"h-4 flex-1\" />\n <Skeleton shape=\"line\" className=\"h-4 flex-1\" />\n <Skeleton shape=\"line\" className=\"h-4 w-16\" />\n </div>\n ))}\n </div>\n )\n}\nTableSkeleton.displayName = 'TableSkeleton'\n\n/* -------------------------------------------------------------------------- */\n/* TableRowSkeleton — a single row inside an existing table */\n/* -------------------------------------------------------------------------- */\n\nexport interface TableRowSkeletonProps {\n /** Number of cells in the row */\n cells?: number\n className?: string\n}\n\nfunction TableRowSkeleton({ cells = 4, className }: TableRowSkeletonProps) {\n // Don't assign role=\"status\" to <tr> — it's a table-row element and\n // ARIA disallows that combination. The cell <td>s carry aria-hidden so\n // screen readers stay quiet during the brief loading window.\n return (\n <tr data-slot=\"table-row-skeleton\" aria-busy=\"true\" className={className}>\n {Array.from({ length: cells }).map((_, i) => (\n <td key={i} className=\"px-3 py-2.5\" aria-hidden=\"true\">\n <Skeleton shape=\"line\" className=\"h-3.5 w-full max-w-[140px]\" />\n </td>\n ))}\n </tr>\n )\n}\nTableRowSkeleton.displayName = 'TableRowSkeleton'\n\n/* -------------------------------------------------------------------------- */\n/* CardSkeleton — typical card with title + body lines + optional image */\n/* -------------------------------------------------------------------------- */\n\nexport interface CardSkeletonProps {\n /** Show a leading image/thumbnail block @default false */\n withImage?: boolean\n /** Aspect ratio of the image (when withImage) @default 'video' */\n imageAspect?: 'video' | 'square' | 'wide'\n /** Number of body lines @default 2 */\n bodyLines?: number\n /** Show a footer row (button-shaped placeholder) @default false */\n withFooter?: boolean\n className?: string\n}\n\nfunction CardSkeleton({\n withImage = false,\n imageAspect = 'video',\n bodyLines = 2,\n withFooter = false,\n className,\n}: CardSkeletonProps) {\n const aspectClass =\n imageAspect === 'square' ? 'aspect-square' : imageAspect === 'wide' ? 'aspect-[21/9]' : 'aspect-video'\n return (\n <div\n data-slot=\"card-skeleton\"\n role=\"status\"\n aria-busy=\"true\"\n aria-label=\"Loading card\"\n className={cn('rounded-lg border border-border bg-card overflow-hidden flex flex-col', className)}\n >\n {withImage && <Skeleton shape=\"rect\" className={cn('w-full', aspectClass)} aria-hidden=\"true\" />}\n <div className=\"p-4 space-y-3 flex-1\">\n <Skeleton shape=\"line\" className=\"h-4 w-3/5\" aria-hidden=\"true\" />\n <div className=\"space-y-2\">\n {Array.from({ length: bodyLines }).map((_, i) => (\n <Skeleton\n key={i}\n shape=\"line\"\n className={cn('h-3', i === bodyLines - 1 ? 'w-2/3' : 'w-full')}\n aria-hidden=\"true\"\n />\n ))}\n </div>\n </div>\n {withFooter && (\n <div className=\"p-4 border-t border-border flex items-center justify-between gap-2\">\n <Skeleton shape=\"line\" className=\"h-3 w-1/4\" aria-hidden=\"true\" />\n <Skeleton shape=\"rect\" className=\"h-8 w-20\" aria-hidden=\"true\" />\n </div>\n )}\n </div>\n )\n}\nCardSkeleton.displayName = 'CardSkeleton'\n\n/* -------------------------------------------------------------------------- */\n/* AvatarTextSkeleton — circle avatar + 2 text lines (list/comment style) */\n/* -------------------------------------------------------------------------- */\n\nexport interface AvatarTextSkeletonProps {\n /** Avatar size @default 'md' */\n size?: 'sm' | 'md' | 'lg'\n /** Number of text lines beside the avatar @default 2 */\n lines?: number\n className?: string\n}\n\nfunction AvatarTextSkeleton({ size = 'md', lines = 2, className }: AvatarTextSkeletonProps) {\n return (\n <div\n data-slot=\"avatar-text-skeleton\"\n role=\"status\"\n aria-busy=\"true\"\n aria-label=\"Loading user\"\n className={cn('flex items-center gap-3', className)}\n >\n <Skeleton shape=\"circle\" size={size} aria-hidden=\"true\" />\n <div className=\"flex-1 min-w-0 space-y-2\">\n {Array.from({ length: lines }).map((_, i) => (\n <Skeleton\n key={i}\n shape=\"line\"\n className={cn('h-3', i === 0 ? 'w-1/3' : i === lines - 1 ? 'w-1/2' : 'w-full')}\n aria-hidden=\"true\"\n />\n ))}\n </div>\n </div>\n )\n}\nAvatarTextSkeleton.displayName = 'AvatarTextSkeleton'\n\n/* -------------------------------------------------------------------------- */\n/* FormRowSkeleton — label + input placeholder for forms */\n/* -------------------------------------------------------------------------- */\n\nexport interface FormRowSkeletonProps {\n /** Field shape — 'input' (one-line), 'textarea' (3-line), 'select' (input + chevron) */\n field?: 'input' | 'textarea' | 'select'\n /** Show help text line below input @default false */\n withHelp?: boolean\n className?: string\n}\n\nfunction FormRowSkeleton({ field = 'input', withHelp = false, className }: FormRowSkeletonProps) {\n const inputClass = field === 'textarea' ? 'h-20' : field === 'select' ? 'h-9' : 'h-9'\n return (\n <div\n data-slot=\"form-row-skeleton\"\n role=\"status\"\n aria-busy=\"true\"\n aria-label=\"Loading form field\"\n className={cn('space-y-2', className)}\n >\n <Skeleton shape=\"line\" className=\"h-3 w-1/4\" aria-hidden=\"true\" />\n <Skeleton shape=\"rect\" className={cn('w-full', inputClass)} aria-hidden=\"true\" />\n {withHelp && <Skeleton shape=\"line\" className=\"h-2.5 w-1/3\" aria-hidden=\"true\" />}\n </div>\n )\n}\nFormRowSkeleton.displayName = 'FormRowSkeleton'\n\nexport {\n Skeleton,\n MetricCardSkeleton,\n ChartSkeleton,\n TableSkeleton,\n TableRowSkeleton,\n CardSkeleton,\n AvatarTextSkeleton,\n FormRowSkeleton,\n}\n"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/lib/utils.ts","../../../src/hooks/use-document-direction.ts","../../../src/components/ui/slider.tsx"],"names":["twMerge","clsx","useState","useEffect","React","jsxs","SliderPrimitive","jsx"],"mappings":"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","file":"slider.cjs","sourcesContent":["import { type ClassValue, clsx } from 'clsx'\nimport { twMerge } from 'tailwind-merge'\nimport { formatJalaliDate } from '@/lib/jalali-utils'\n\nexport function cn(...inputs: ClassValue[]) {\n return twMerge(clsx(inputs))\n}\n\nexport type SupportedLocale = 'fa' | 'ar' | 'en'\n\n/**\n * Convert digits in a string to Persian/Arabic numerals based on locale.\n * @example convertToLocalNumbers('123', 'fa') => '۱۲۳'\n * @example convertToLocalNumbers('123', 'en') => '123'\n */\nexport function convertToLocalNumbers(text: string | number, locale: SupportedLocale): string {\n if (locale === 'fa' || locale === 'ar') {\n const persianDigits = ['۰', '۱', '۲', '۳', '۴', '۵', '۶', '۷', '۸', '۹']\n return String(text).replace(/\\d/g, (digit) => persianDigits[parseInt(digit)])\n }\n return String(text)\n}\n\n/**\n * Format large numbers with locale-aware suffixes (K/M/B).\n * @example formatLargeNumber(1500, 'fa') => '۱.۵ هزار'\n * @example formatLargeNumber(1500, 'en') => '1.5K'\n */\nexport function formatLargeNumber(num: number, locale: SupportedLocale): string {\n if (num >= 1_000_000_000) {\n const formatted = (num / 1_000_000_000).toFixed(1).replace(/\\.0$/, '')\n return convertToLocalNumbers(formatted, locale) + (locale === 'en' ? 'B' : ' میلیارد')\n }\n if (num >= 1_000_000) {\n const formatted = (num / 1_000_000).toFixed(1).replace(/\\.0$/, '')\n return convertToLocalNumbers(formatted, locale) + (locale === 'en' ? 'M' : ' میلیون')\n }\n if (num >= 1_000) {\n const formatted = (num / 1_000).toFixed(1).replace(/\\.0$/, '')\n return convertToLocalNumbers(formatted, locale) + (locale === 'en' ? 'K' : ' هزار')\n }\n return convertToLocalNumbers(num.toString().replace(/\\B(?=(\\d{3})+(?!\\d))/g, ','), locale)\n}\n\n/**\n * Format number to Instagram-style short format (English only).\n * @example formatNumber(123456, 'short') => '123K'\n * @example formatNumber(123456, 'exact') => '123,456'\n */\nexport function formatNumber(num: number | undefined, format: 'exact' | 'short' = 'exact'): string {\n if (num === undefined || num === null) return '0'\n\n if (format === 'exact') {\n return num.toLocaleString('en-US')\n }\n\n // Short format (Instagram style)\n if (num >= 1_000_000_000) {\n return `${(num / 1_000_000_000).toFixed(1).replace(/\\.0$/, '')}B`\n }\n if (num >= 1_000_000) {\n return `${(num / 1_000_000).toFixed(1).replace(/\\.0$/, '')}M`\n }\n if (num >= 1_000) {\n return `${(num / 1_000).toFixed(1).replace(/\\.0$/, '')}K`\n }\n return num.toString()\n}\n\n/**\n * Format date to relative time with absolute on hover (Persian)\n * @example formatRelativeTime(new Date()) => '۲ ساعت پیش'\n */\nexport function formatRelativeTime(date: Date | string | number): string {\n const now = new Date()\n const then = new Date(date)\n const diffInSeconds = Math.floor((now.getTime() - then.getTime()) / 1000)\n\n if (diffInSeconds < 60) {\n return 'همین الان'\n }\n\n const diffInMinutes = Math.floor(diffInSeconds / 60)\n if (diffInMinutes < 60) {\n return `${convertToLocalNumbers(diffInMinutes, 'fa')} دقیقه پیش`\n }\n\n const diffInHours = Math.floor(diffInMinutes / 60)\n if (diffInHours < 24) {\n return `${convertToLocalNumbers(diffInHours, 'fa')} ساعت پیش`\n }\n\n const diffInDays = Math.floor(diffInHours / 24)\n if (diffInDays < 7) {\n return `${convertToLocalNumbers(diffInDays, 'fa')} روز پیش`\n }\n\n const diffInWeeks = Math.floor(diffInDays / 7)\n if (diffInWeeks < 4) {\n return `${convertToLocalNumbers(diffInWeeks, 'fa')} هفته پیش`\n }\n\n const diffInMonths = Math.floor(diffInDays / 30)\n if (diffInMonths < 12) {\n return `${convertToLocalNumbers(diffInMonths, 'fa')} ماه پیش`\n }\n\n const diffInYears = Math.floor(diffInDays / 365)\n return `${convertToLocalNumbers(diffInYears, 'fa')} سال پیش`\n}\n\n/**\n * Format date to absolute format (Persian / Jalali)\n * Uses date-fns-jalali for accurate Jalali conversion.\n * @example formatAbsoluteTime(new Date()) => '۱۵ دی ۱۴۰۳، ۱۵:۳۰'\n */\nexport function formatAbsoluteTime(date: Date | string | number): string {\n const d = new Date(date)\n return formatJalaliDate(d, 'd MMMM yyyy، HH:mm')\n}\n","'use client'\n\nimport { useState, useEffect } from 'react'\n\ntype Direction = 'ltr' | 'rtl'\n\nfunction useDocumentDirection(): Direction {\n const [direction, setDirection] = useState<Direction>('rtl')\n\n useEffect(() => {\n const getDirection = (): Direction => {\n if (typeof document === 'undefined') return 'rtl'\n const htmlDir = document.documentElement.getAttribute('dir')\n if (htmlDir === 'ltr' || htmlDir === 'rtl') {\n return htmlDir\n }\n const computedDir = window.getComputedStyle(document.documentElement).direction\n return computedDir === 'rtl' ? 'rtl' : 'ltr'\n }\n\n setDirection(getDirection())\n\n const observer = new MutationObserver(() => {\n setDirection(getDirection())\n })\n\n observer.observe(document.documentElement, {\n attributes: true,\n attributeFilter: ['dir'],\n })\n\n return () => observer.disconnect()\n }, [])\n\n return direction\n}\n\nexport { useDocumentDirection }\nexport type { Direction }\n","'use client'\n\nimport * as React from 'react'\nimport * as SliderPrimitive from '@radix-ui/react-slider'\n\nimport { cn } from '@/lib/utils'\nimport { useDocumentDirection, type Direction } from '@/hooks/use-document-direction'\n\nfunction Slider({\n className,\n defaultValue,\n value,\n min = 0,\n max = 100,\n dir,\n 'aria-label': ariaLabel,\n 'aria-labelledby': ariaLabelledBy,\n ...props\n}: React.ComponentProps<typeof SliderPrimitive.Root>) {\n const _values = React.useMemo(\n () => (Array.isArray(value) ? value : Array.isArray(defaultValue) ? defaultValue : [min, max]),\n [value, defaultValue, min, max]\n )\n const documentDir = useDocumentDirection()\n const resolvedDir = (dir as Direction) ?? documentDir\n\n // Radix scopes aria-label/labelledby to the Root, but axe (and screen readers)\n // need them on each Thumb since the thumb is the actual aria input. Propagate\n // down. For multi-thumb sliders, consumers should pass per-thumb labels via\n // an array; we suffix with the thumb index in that case for disambiguation.\n const resolveThumbLabel = (index: number): string | undefined => {\n if (!ariaLabel) return undefined\n if (_values.length === 1) return ariaLabel\n return `${ariaLabel} ${index + 1}`\n }\n\n return (\n <SliderPrimitive.Root\n data-slot=\"slider\"\n dir={resolvedDir}\n defaultValue={defaultValue}\n value={value}\n min={min}\n max={max}\n className={cn(\n 'relative flex w-full touch-none items-center select-none data-[disabled]:opacity-50 data-[orientation=vertical]:h-full data-[orientation=vertical]:min-h-44 data-[orientation=vertical]:w-auto data-[orientation=vertical]:flex-col',\n className\n )}\n {...props}\n >\n <SliderPrimitive.Track\n data-slot=\"slider-track\"\n className={cn(\n 'bg-muted relative grow overflow-hidden rounded-full data-[orientation=horizontal]:h-1.5 data-[orientation=horizontal]:w-full data-[orientation=vertical]:h-full data-[orientation=vertical]:w-1.5'\n )}\n >\n <SliderPrimitive.Range\n data-slot=\"slider-range\"\n className={cn('bg-primary absolute data-[orientation=horizontal]:h-full data-[orientation=vertical]:w-full')}\n />\n </SliderPrimitive.Track>\n {Array.from({ length: _values.length }, (_, index) => (\n <SliderPrimitive.Thumb\n data-slot=\"slider-thumb\"\n key={index}\n aria-label={resolveThumbLabel(index)}\n aria-labelledby={ariaLabelledBy}\n className=\"border-primary ring-ring/50 block size-4 shrink-0 rounded-full border bg-background-surface-75 shadow-sm transition-all duration-150 hover:ring-4 focus-visible:ring-4 focus-visible:outline-hidden disabled:pointer-events-none disabled:opacity-50\"\n />\n ))}\n </SliderPrimitive.Root>\n )\n}\n\nSlider.displayName = 'Slider'\n\nexport { Slider }\n"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/lib/utils.ts","../../../src/hooks/use-document-direction.ts","../../../src/components/ui/slider.tsx"],"names":[],"mappings":"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","file":"slider.js","sourcesContent":["import { type ClassValue, clsx } from 'clsx'\nimport { twMerge } from 'tailwind-merge'\nimport { formatJalaliDate } from '@/lib/jalali-utils'\n\nexport function cn(...inputs: ClassValue[]) {\n return twMerge(clsx(inputs))\n}\n\nexport type SupportedLocale = 'fa' | 'ar' | 'en'\n\n/**\n * Convert digits in a string to Persian/Arabic numerals based on locale.\n * @example convertToLocalNumbers('123', 'fa') => '۱۲۳'\n * @example convertToLocalNumbers('123', 'en') => '123'\n */\nexport function convertToLocalNumbers(text: string | number, locale: SupportedLocale): string {\n if (locale === 'fa' || locale === 'ar') {\n const persianDigits = ['۰', '۱', '۲', '۳', '۴', '۵', '۶', '۷', '۸', '۹']\n return String(text).replace(/\\d/g, (digit) => persianDigits[parseInt(digit)])\n }\n return String(text)\n}\n\n/**\n * Format large numbers with locale-aware suffixes (K/M/B).\n * @example formatLargeNumber(1500, 'fa') => '۱.۵ هزار'\n * @example formatLargeNumber(1500, 'en') => '1.5K'\n */\nexport function formatLargeNumber(num: number, locale: SupportedLocale): string {\n if (num >= 1_000_000_000) {\n const formatted = (num / 1_000_000_000).toFixed(1).replace(/\\.0$/, '')\n return convertToLocalNumbers(formatted, locale) + (locale === 'en' ? 'B' : ' میلیارد')\n }\n if (num >= 1_000_000) {\n const formatted = (num / 1_000_000).toFixed(1).replace(/\\.0$/, '')\n return convertToLocalNumbers(formatted, locale) + (locale === 'en' ? 'M' : ' میلیون')\n }\n if (num >= 1_000) {\n const formatted = (num / 1_000).toFixed(1).replace(/\\.0$/, '')\n return convertToLocalNumbers(formatted, locale) + (locale === 'en' ? 'K' : ' هزار')\n }\n return convertToLocalNumbers(num.toString().replace(/\\B(?=(\\d{3})+(?!\\d))/g, ','), locale)\n}\n\n/**\n * Format number to Instagram-style short format (English only).\n * @example formatNumber(123456, 'short') => '123K'\n * @example formatNumber(123456, 'exact') => '123,456'\n */\nexport function formatNumber(num: number | undefined, format: 'exact' | 'short' = 'exact'): string {\n if (num === undefined || num === null) return '0'\n\n if (format === 'exact') {\n return num.toLocaleString('en-US')\n }\n\n // Short format (Instagram style)\n if (num >= 1_000_000_000) {\n return `${(num / 1_000_000_000).toFixed(1).replace(/\\.0$/, '')}B`\n }\n if (num >= 1_000_000) {\n return `${(num / 1_000_000).toFixed(1).replace(/\\.0$/, '')}M`\n }\n if (num >= 1_000) {\n return `${(num / 1_000).toFixed(1).replace(/\\.0$/, '')}K`\n }\n return num.toString()\n}\n\n/**\n * Format date to relative time with absolute on hover (Persian)\n * @example formatRelativeTime(new Date()) => '۲ ساعت پیش'\n */\nexport function formatRelativeTime(date: Date | string | number): string {\n const now = new Date()\n const then = new Date(date)\n const diffInSeconds = Math.floor((now.getTime() - then.getTime()) / 1000)\n\n if (diffInSeconds < 60) {\n return 'همین الان'\n }\n\n const diffInMinutes = Math.floor(diffInSeconds / 60)\n if (diffInMinutes < 60) {\n return `${convertToLocalNumbers(diffInMinutes, 'fa')} دقیقه پیش`\n }\n\n const diffInHours = Math.floor(diffInMinutes / 60)\n if (diffInHours < 24) {\n return `${convertToLocalNumbers(diffInHours, 'fa')} ساعت پیش`\n }\n\n const diffInDays = Math.floor(diffInHours / 24)\n if (diffInDays < 7) {\n return `${convertToLocalNumbers(diffInDays, 'fa')} روز پیش`\n }\n\n const diffInWeeks = Math.floor(diffInDays / 7)\n if (diffInWeeks < 4) {\n return `${convertToLocalNumbers(diffInWeeks, 'fa')} هفته پیش`\n }\n\n const diffInMonths = Math.floor(diffInDays / 30)\n if (diffInMonths < 12) {\n return `${convertToLocalNumbers(diffInMonths, 'fa')} ماه پیش`\n }\n\n const diffInYears = Math.floor(diffInDays / 365)\n return `${convertToLocalNumbers(diffInYears, 'fa')} سال پیش`\n}\n\n/**\n * Format date to absolute format (Persian / Jalali)\n * Uses date-fns-jalali for accurate Jalali conversion.\n * @example formatAbsoluteTime(new Date()) => '۱۵ دی ۱۴۰۳، ۱۵:۳۰'\n */\nexport function formatAbsoluteTime(date: Date | string | number): string {\n const d = new Date(date)\n return formatJalaliDate(d, 'd MMMM yyyy، HH:mm')\n}\n","'use client'\n\nimport { useState, useEffect } from 'react'\n\ntype Direction = 'ltr' | 'rtl'\n\nfunction useDocumentDirection(): Direction {\n const [direction, setDirection] = useState<Direction>('rtl')\n\n useEffect(() => {\n const getDirection = (): Direction => {\n if (typeof document === 'undefined') return 'rtl'\n const htmlDir = document.documentElement.getAttribute('dir')\n if (htmlDir === 'ltr' || htmlDir === 'rtl') {\n return htmlDir\n }\n const computedDir = window.getComputedStyle(document.documentElement).direction\n return computedDir === 'rtl' ? 'rtl' : 'ltr'\n }\n\n setDirection(getDirection())\n\n const observer = new MutationObserver(() => {\n setDirection(getDirection())\n })\n\n observer.observe(document.documentElement, {\n attributes: true,\n attributeFilter: ['dir'],\n })\n\n return () => observer.disconnect()\n }, [])\n\n return direction\n}\n\nexport { useDocumentDirection }\nexport type { Direction }\n","'use client'\n\nimport * as React from 'react'\nimport * as SliderPrimitive from '@radix-ui/react-slider'\n\nimport { cn } from '@/lib/utils'\nimport { useDocumentDirection, type Direction } from '@/hooks/use-document-direction'\n\nfunction Slider({\n className,\n defaultValue,\n value,\n min = 0,\n max = 100,\n dir,\n 'aria-label': ariaLabel,\n 'aria-labelledby': ariaLabelledBy,\n ...props\n}: React.ComponentProps<typeof SliderPrimitive.Root>) {\n const _values = React.useMemo(\n () => (Array.isArray(value) ? value : Array.isArray(defaultValue) ? defaultValue : [min, max]),\n [value, defaultValue, min, max]\n )\n const documentDir = useDocumentDirection()\n const resolvedDir = (dir as Direction) ?? documentDir\n\n // Radix scopes aria-label/labelledby to the Root, but axe (and screen readers)\n // need them on each Thumb since the thumb is the actual aria input. Propagate\n // down. For multi-thumb sliders, consumers should pass per-thumb labels via\n // an array; we suffix with the thumb index in that case for disambiguation.\n const resolveThumbLabel = (index: number): string | undefined => {\n if (!ariaLabel) return undefined\n if (_values.length === 1) return ariaLabel\n return `${ariaLabel} ${index + 1}`\n }\n\n return (\n <SliderPrimitive.Root\n data-slot=\"slider\"\n dir={resolvedDir}\n defaultValue={defaultValue}\n value={value}\n min={min}\n max={max}\n className={cn(\n 'relative flex w-full touch-none items-center select-none data-[disabled]:opacity-50 data-[orientation=vertical]:h-full data-[orientation=vertical]:min-h-44 data-[orientation=vertical]:w-auto data-[orientation=vertical]:flex-col',\n className\n )}\n {...props}\n >\n <SliderPrimitive.Track\n data-slot=\"slider-track\"\n className={cn(\n 'bg-muted relative grow overflow-hidden rounded-full data-[orientation=horizontal]:h-1.5 data-[orientation=horizontal]:w-full data-[orientation=vertical]:h-full data-[orientation=vertical]:w-1.5'\n )}\n >\n <SliderPrimitive.Range\n data-slot=\"slider-range\"\n className={cn('bg-primary absolute data-[orientation=horizontal]:h-full data-[orientation=vertical]:w-full')}\n />\n </SliderPrimitive.Track>\n {Array.from({ length: _values.length }, (_, index) => (\n <SliderPrimitive.Thumb\n data-slot=\"slider-thumb\"\n key={index}\n aria-label={resolveThumbLabel(index)}\n aria-labelledby={ariaLabelledBy}\n className=\"border-primary ring-ring/50 block size-4 shrink-0 rounded-full border bg-background-surface-75 shadow-sm transition-all duration-150 hover:ring-4 focus-visible:ring-4 focus-visible:outline-hidden disabled:pointer-events-none disabled:opacity-50\"\n />\n ))}\n </SliderPrimitive.Root>\n )\n}\n\nSlider.displayName = 'Slider'\n\nexport { Slider }\n"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/lib/utils.ts","../../../src/components/ui/social-platform-badge.tsx"],"names":["twMerge","clsx","jsx","cva","React","jsxs"],"mappings":"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","file":"social-platform-badge.cjs","sourcesContent":["import { type ClassValue, clsx } from 'clsx'\nimport { twMerge } from 'tailwind-merge'\nimport { formatJalaliDate } from '@/lib/jalali-utils'\n\nexport function cn(...inputs: ClassValue[]) {\n return twMerge(clsx(inputs))\n}\n\nexport type SupportedLocale = 'fa' | 'ar' | 'en'\n\n/**\n * Convert digits in a string to Persian/Arabic numerals based on locale.\n * @example convertToLocalNumbers('123', 'fa') => '۱۲۳'\n * @example convertToLocalNumbers('123', 'en') => '123'\n */\nexport function convertToLocalNumbers(text: string | number, locale: SupportedLocale): string {\n if (locale === 'fa' || locale === 'ar') {\n const persianDigits = ['۰', '۱', '۲', '۳', '۴', '۵', '۶', '۷', '۸', '۹']\n return String(text).replace(/\\d/g, (digit) => persianDigits[parseInt(digit)])\n }\n return String(text)\n}\n\n/**\n * Format large numbers with locale-aware suffixes (K/M/B).\n * @example formatLargeNumber(1500, 'fa') => '۱.۵ هزار'\n * @example formatLargeNumber(1500, 'en') => '1.5K'\n */\nexport function formatLargeNumber(num: number, locale: SupportedLocale): string {\n if (num >= 1_000_000_000) {\n const formatted = (num / 1_000_000_000).toFixed(1).replace(/\\.0$/, '')\n return convertToLocalNumbers(formatted, locale) + (locale === 'en' ? 'B' : ' میلیارد')\n }\n if (num >= 1_000_000) {\n const formatted = (num / 1_000_000).toFixed(1).replace(/\\.0$/, '')\n return convertToLocalNumbers(formatted, locale) + (locale === 'en' ? 'M' : ' میلیون')\n }\n if (num >= 1_000) {\n const formatted = (num / 1_000).toFixed(1).replace(/\\.0$/, '')\n return convertToLocalNumbers(formatted, locale) + (locale === 'en' ? 'K' : ' هزار')\n }\n return convertToLocalNumbers(num.toString().replace(/\\B(?=(\\d{3})+(?!\\d))/g, ','), locale)\n}\n\n/**\n * Format number to Instagram-style short format (English only).\n * @example formatNumber(123456, 'short') => '123K'\n * @example formatNumber(123456, 'exact') => '123,456'\n */\nexport function formatNumber(num: number | undefined, format: 'exact' | 'short' = 'exact'): string {\n if (num === undefined || num === null) return '0'\n\n if (format === 'exact') {\n return num.toLocaleString('en-US')\n }\n\n // Short format (Instagram style)\n if (num >= 1_000_000_000) {\n return `${(num / 1_000_000_000).toFixed(1).replace(/\\.0$/, '')}B`\n }\n if (num >= 1_000_000) {\n return `${(num / 1_000_000).toFixed(1).replace(/\\.0$/, '')}M`\n }\n if (num >= 1_000) {\n return `${(num / 1_000).toFixed(1).replace(/\\.0$/, '')}K`\n }\n return num.toString()\n}\n\n/**\n * Format date to relative time with absolute on hover (Persian)\n * @example formatRelativeTime(new Date()) => '۲ ساعت پیش'\n */\nexport function formatRelativeTime(date: Date | string | number): string {\n const now = new Date()\n const then = new Date(date)\n const diffInSeconds = Math.floor((now.getTime() - then.getTime()) / 1000)\n\n if (diffInSeconds < 60) {\n return 'همین الان'\n }\n\n const diffInMinutes = Math.floor(diffInSeconds / 60)\n if (diffInMinutes < 60) {\n return `${convertToLocalNumbers(diffInMinutes, 'fa')} دقیقه پیش`\n }\n\n const diffInHours = Math.floor(diffInMinutes / 60)\n if (diffInHours < 24) {\n return `${convertToLocalNumbers(diffInHours, 'fa')} ساعت پیش`\n }\n\n const diffInDays = Math.floor(diffInHours / 24)\n if (diffInDays < 7) {\n return `${convertToLocalNumbers(diffInDays, 'fa')} روز پیش`\n }\n\n const diffInWeeks = Math.floor(diffInDays / 7)\n if (diffInWeeks < 4) {\n return `${convertToLocalNumbers(diffInWeeks, 'fa')} هفته پیش`\n }\n\n const diffInMonths = Math.floor(diffInDays / 30)\n if (diffInMonths < 12) {\n return `${convertToLocalNumbers(diffInMonths, 'fa')} ماه پیش`\n }\n\n const diffInYears = Math.floor(diffInDays / 365)\n return `${convertToLocalNumbers(diffInYears, 'fa')} سال پیش`\n}\n\n/**\n * Format date to absolute format (Persian / Jalali)\n * Uses date-fns-jalali for accurate Jalali conversion.\n * @example formatAbsoluteTime(new Date()) => '۱۵ دی ۱۴۰۳، ۱۵:۳۰'\n */\nexport function formatAbsoluteTime(date: Date | string | number): string {\n const d = new Date(date)\n return formatJalaliDate(d, 'd MMMM yyyy، HH:mm')\n}\n","'use client'\n\nimport * as React from 'react'\nimport { cva } from 'class-variance-authority'\nimport { cn } from '@/lib/utils'\n\nexport type SocialPlatform =\n | 'instagram'\n | 'twitter'\n | 'tiktok'\n | 'youtube'\n | 'linkedin'\n | 'telegram'\n | 'threads'\n | 'facebook'\n\nexport interface SocialPlatformBadgeProps extends React.HTMLAttributes<HTMLSpanElement> {\n /** The social media platform to display */\n platform: SocialPlatform\n /** Size of the badge */\n size?: 'xs' | 'sm' | 'md' | 'lg'\n /** Show the platform name label */\n showLabel?: boolean\n /** Visual style of the badge */\n variant?: 'flat' | 'badge'\n}\n\nconst PLATFORM_META: Record<\n SocialPlatform,\n { label: string; icon: React.FC<{ className?: string; style?: React.CSSProperties }> }\n> = {\n instagram: {\n label: 'Instagram',\n icon: ({ className }) => (\n <svg className={className} viewBox=\"0 0 24 24\" fill=\"currentColor\" aria-hidden=\"true\">\n <path d=\"M12 2.163c3.204 0 3.584.012 4.85.07 3.252.148 4.771 1.691 4.919 4.919.058 1.265.069 1.645.069 4.849 0 3.205-.012 3.584-.069 4.849-.149 3.225-1.664 4.771-4.919 4.919-1.266.058-1.644.07-4.85.07-3.204 0-3.584-.012-4.849-.07-3.26-.149-4.771-1.699-4.919-4.92-.058-1.265-.07-1.644-.07-4.849 0-3.204.013-3.583.07-4.849.149-3.227 1.664-4.771 4.919-4.919 1.266-.057 1.645-.069 4.849-.069zm0-2.163c-3.259 0-3.667.014-4.947.072-4.358.2-6.78 2.618-6.98 6.98-.059 1.281-.073 1.689-.073 4.948 0 3.259.014 3.668.072 4.948.2 4.358 2.618 6.78 6.98 6.98 1.281.058 1.689.072 4.948.072 3.259 0 3.668-.014 4.948-.072 4.354-.2 6.782-2.618 6.979-6.98.059-1.28.073-1.689.073-4.948 0-3.259-.014-3.667-.072-4.947-.196-4.354-2.617-6.78-6.979-6.98-1.281-.059-1.69-.073-4.949-.073zm0 5.838c-3.403 0-6.162 2.759-6.162 6.162s2.759 6.163 6.162 6.163 6.162-2.759 6.162-6.163c0-3.403-2.759-6.162-6.162-6.162zm0 10.162c-2.209 0-4-1.79-4-4 0-2.209 1.791-4 4-4s4 1.791 4 4c0 2.21-1.791 4-4 4zm6.406-11.845c-.796 0-1.441.645-1.441 1.44s.645 1.44 1.441 1.44c.795 0 1.439-.645 1.439-1.44s-.644-1.44-1.439-1.44z\" />\n </svg>\n ),\n },\n twitter: {\n label: 'X',\n icon: ({ className }) => (\n <svg className={className} viewBox=\"0 0 24 24\" fill=\"currentColor\" aria-hidden=\"true\">\n <path d=\"M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-4.714-6.231-5.401 6.231H2.744l7.737-8.835L1.254 2.25H8.08l4.259 5.63 5.905-5.63zm-1.161 17.52h1.833L7.084 4.126H5.117z\" />\n </svg>\n ),\n },\n tiktok: {\n label: 'TikTok',\n icon: ({ className }) => (\n <svg className={className} viewBox=\"0 0 24 24\" fill=\"currentColor\" aria-hidden=\"true\">\n <path d=\"M19.59 6.69a4.83 4.83 0 01-3.77-4.25V2h-3.45v13.67a2.89 2.89 0 01-2.88 2.5 2.89 2.89 0 01-2.89-2.89 2.89 2.89 0 012.89-2.89c.28 0 .54.04.79.1V9.01a6.33 6.33 0 00-.79-.05 6.34 6.34 0 00-6.34 6.34 6.34 6.34 0 006.34 6.34 6.34 6.34 0 006.33-6.34V8.69a8.16 8.16 0 004.77 1.52V6.75a4.85 4.85 0 01-1-.06z\" />\n </svg>\n ),\n },\n youtube: {\n label: 'YouTube',\n icon: ({ className }) => (\n <svg className={className} viewBox=\"0 0 24 24\" fill=\"currentColor\" aria-hidden=\"true\">\n <path d=\"M23.498 6.186a3.016 3.016 0 00-2.122-2.136C19.505 3.545 12 3.545 12 3.545s-7.505 0-9.377.505A3.017 3.017 0 00.502 6.186C0 8.07 0 12 0 12s0 3.93.502 5.814a3.016 3.016 0 002.122 2.136c1.871.505 9.376.505 9.376.505s7.505 0 9.377-.505a3.015 3.015 0 002.122-2.136C24 15.93 24 12 24 12s0-3.93-.502-5.814zM9.545 15.568V8.432L15.818 12l-6.273 3.568z\" />\n </svg>\n ),\n },\n linkedin: {\n label: 'LinkedIn',\n icon: ({ className }) => (\n <svg className={className} viewBox=\"0 0 24 24\" fill=\"currentColor\" aria-hidden=\"true\">\n <path d=\"M20.447 20.452h-3.554v-5.569c0-1.328-.027-3.037-1.852-3.037-1.853 0-2.136 1.445-2.136 2.939v5.667H9.351V9h3.414v1.561h.046c.477-.9 1.637-1.85 3.37-1.85 3.601 0 4.267 2.37 4.267 5.455v6.286zM5.337 7.433a2.062 2.062 0 01-2.063-2.065 2.064 2.064 0 112.063 2.065zm1.782 13.019H3.555V9h3.564v11.452zM22.225 0H1.771C.792 0 0 .774 0 1.729v20.542C0 23.227.792 24 1.771 24h20.451C23.2 24 24 23.227 24 22.271V1.729C24 .774 23.2 0 22.222 0h.003z\" />\n </svg>\n ),\n },\n telegram: {\n label: 'Telegram',\n icon: ({ className }) => (\n <svg className={className} viewBox=\"0 0 24 24\" fill=\"currentColor\" aria-hidden=\"true\">\n <path d=\"M11.944 0A12 12 0 000 12a12 12 0 0012 12 12 12 0 0012-12A12 12 0 0012 0a12 12 0 00-.056 0zm4.962 7.224c.1-.002.321.023.465.14a.506.506 0 01.171.325c.016.093.036.306.02.472-.18 1.898-.962 6.502-1.36 8.627-.168.9-.499 1.201-.82 1.23-.696.065-1.225-.46-1.9-.902-1.056-.693-1.653-1.124-2.678-1.8-1.185-.78-.417-1.21.258-1.91.177-.184 3.247-2.977 3.307-3.23.007-.032.014-.15-.056-.212s-.174-.041-.249-.024c-.106.024-1.793 1.14-5.061 3.345-.48.33-.913.49-1.302.48-.428-.008-1.252-.241-1.865-.44-.752-.245-1.349-.374-1.297-.789.027-.216.325-.437.893-.663 3.498-1.524 5.83-2.529 6.998-3.014 3.332-1.386 4.025-1.627 4.476-1.635z\" />\n </svg>\n ),\n },\n threads: {\n label: 'Threads',\n icon: ({ className }) => (\n <svg className={className} viewBox=\"0 0 24 24\" fill=\"currentColor\" aria-hidden=\"true\">\n <path d=\"M12.186 24h-.007c-3.581-.024-6.334-1.205-8.184-3.509C2.35 18.44 1.5 15.586 1.472 12.01v-.017c.03-3.579.879-6.43 2.525-8.482C5.868 1.205 8.617.024 12.18 0h.014c2.746.02 5.043.725 6.826 2.098 1.677 1.29 2.858 3.13 3.509 5.467l-2.04.569c-1.104-3.96-3.898-5.984-8.304-6.015-2.91.022-5.11.936-6.54 2.717C4.307 6.504 3.616 8.914 3.589 12c.027 3.086.718 5.496 2.057 7.164 1.43 1.783 3.631 2.698 6.54 2.717 2.623-.02 4.358-.631 5.8-2.045 1.647-1.613 1.618-3.593 1.09-4.798-.31-.71-.873-1.3-1.634-1.75-.192 1.352-.622 2.446-1.284 3.272-.886 1.102-2.14 1.704-3.73 1.79-1.202.065-2.361-.218-3.259-.801-1.063-.689-1.685-1.749-1.752-2.98-.065-1.19.388-2.285 1.277-3.086.84-.754 2.02-1.164 3.43-1.19.954-.018 1.858.09 2.695.322-.082-.858-.372-1.502-.861-1.903-.529-.434-1.32-.658-2.332-.67-.884.01-1.63.23-2.22.65l-1.11-1.75c.97-.625 2.18-.95 3.6-.97h.022c1.63.02 2.976.497 3.898 1.385.955.92 1.444 2.25 1.51 3.95.23.073.46.149.682.232 1.69.64 2.847 1.635 3.406 2.946.859 2.002.86 5.139-1.773 7.674-1.797 1.742-3.993 2.525-6.935 2.545zm.098-10.02c-.745.01-1.377.186-1.882.523-.443.298-.706.733-.67 1.215.03.564.342 1.017.876 1.275.516.25 1.087.299 1.58.127.684-.24 1.34-.93 1.614-2.366a9.68 9.68 0 00-1.518-.774z\" />\n </svg>\n ),\n },\n facebook: {\n label: 'Facebook',\n icon: ({ className }) => (\n <svg className={className} viewBox=\"0 0 24 24\" fill=\"currentColor\" aria-hidden=\"true\">\n <path d=\"M24 12.073c0-6.627-5.373-12-12-12s-12 5.373-12 12c0 5.99 4.388 10.954 10.125 11.854v-8.385H7.078v-3.47h3.047V9.43c0-3.007 1.792-4.669 4.533-4.669 1.312 0 2.686.235 2.686.235v2.953H15.83c-1.491 0-1.956.925-1.956 1.875v2.25h3.328l-.532 3.47h-2.796v8.385C19.612 23.027 24 18.062 24 12.073z\" />\n </svg>\n ),\n },\n}\n\nconst socialPlatformBadgeVariants = cva('inline-flex items-center font-medium transition-colors duration-150', {\n variants: {\n size: {\n xs: 'gap-1',\n sm: 'gap-1.5',\n md: 'gap-1.5',\n lg: 'gap-2',\n },\n variant: {\n flat: '',\n badge: 'border border-border bg-surface-100',\n },\n },\n compoundVariants: [\n { variant: 'badge', size: 'xs', className: 'px-1.5 py-0.5 rounded' },\n { variant: 'badge', size: 'sm', className: 'px-2 py-1 rounded' },\n { variant: 'badge', size: 'md', className: 'px-2.5 py-1 rounded-md' },\n { variant: 'badge', size: 'lg', className: 'px-3 py-1.5 rounded-md' },\n ],\n defaultVariants: { size: 'sm', variant: 'flat' },\n})\n\n/** Size-specific classes for child elements (icon, text) */\nconst SIZE_CHILD_CLASSES = {\n xs: { icon: 'size-3', text: 'text-xs' },\n sm: { icon: 'size-4', text: 'text-xs' },\n md: { icon: 'size-5', text: 'text-sm' },\n lg: { icon: 'size-6', text: 'text-base' },\n}\n\nconst SocialPlatformBadge = React.forwardRef<HTMLSpanElement, SocialPlatformBadgeProps>(\n ({ className, platform, size = 'sm', showLabel = false, variant = 'flat', style, ...props }, ref) => {\n const meta = PLATFORM_META[platform]\n const Icon = meta.icon\n const sizeClasses = SIZE_CHILD_CLASSES[size]\n\n return (\n <span\n ref={ref}\n data-slot=\"social-platform-badge\"\n className={cn(socialPlatformBadgeVariants({ size, variant }), className)}\n style={style}\n {...props}\n >\n <Icon className={cn(sizeClasses.icon, 'shrink-0')} style={{ color: `var(--social-platform-${platform})` }} />\n {showLabel && <span className={cn(sizeClasses.text, 'text-foreground-light')}>{meta.label}</span>}\n </span>\n )\n }\n)\n\nSocialPlatformBadge.displayName = 'SocialPlatformBadge'\n\nexport { SocialPlatformBadge, socialPlatformBadgeVariants }\n"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/lib/utils.ts","../../../src/components/ui/social-platform-badge.tsx"],"names":[],"mappings":"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","file":"social-platform-badge.js","sourcesContent":["import { type ClassValue, clsx } from 'clsx'\nimport { twMerge } from 'tailwind-merge'\nimport { formatJalaliDate } from '@/lib/jalali-utils'\n\nexport function cn(...inputs: ClassValue[]) {\n return twMerge(clsx(inputs))\n}\n\nexport type SupportedLocale = 'fa' | 'ar' | 'en'\n\n/**\n * Convert digits in a string to Persian/Arabic numerals based on locale.\n * @example convertToLocalNumbers('123', 'fa') => '۱۲۳'\n * @example convertToLocalNumbers('123', 'en') => '123'\n */\nexport function convertToLocalNumbers(text: string | number, locale: SupportedLocale): string {\n if (locale === 'fa' || locale === 'ar') {\n const persianDigits = ['۰', '۱', '۲', '۳', '۴', '۵', '۶', '۷', '۸', '۹']\n return String(text).replace(/\\d/g, (digit) => persianDigits[parseInt(digit)])\n }\n return String(text)\n}\n\n/**\n * Format large numbers with locale-aware suffixes (K/M/B).\n * @example formatLargeNumber(1500, 'fa') => '۱.۵ هزار'\n * @example formatLargeNumber(1500, 'en') => '1.5K'\n */\nexport function formatLargeNumber(num: number, locale: SupportedLocale): string {\n if (num >= 1_000_000_000) {\n const formatted = (num / 1_000_000_000).toFixed(1).replace(/\\.0$/, '')\n return convertToLocalNumbers(formatted, locale) + (locale === 'en' ? 'B' : ' میلیارد')\n }\n if (num >= 1_000_000) {\n const formatted = (num / 1_000_000).toFixed(1).replace(/\\.0$/, '')\n return convertToLocalNumbers(formatted, locale) + (locale === 'en' ? 'M' : ' میلیون')\n }\n if (num >= 1_000) {\n const formatted = (num / 1_000).toFixed(1).replace(/\\.0$/, '')\n return convertToLocalNumbers(formatted, locale) + (locale === 'en' ? 'K' : ' هزار')\n }\n return convertToLocalNumbers(num.toString().replace(/\\B(?=(\\d{3})+(?!\\d))/g, ','), locale)\n}\n\n/**\n * Format number to Instagram-style short format (English only).\n * @example formatNumber(123456, 'short') => '123K'\n * @example formatNumber(123456, 'exact') => '123,456'\n */\nexport function formatNumber(num: number | undefined, format: 'exact' | 'short' = 'exact'): string {\n if (num === undefined || num === null) return '0'\n\n if (format === 'exact') {\n return num.toLocaleString('en-US')\n }\n\n // Short format (Instagram style)\n if (num >= 1_000_000_000) {\n return `${(num / 1_000_000_000).toFixed(1).replace(/\\.0$/, '')}B`\n }\n if (num >= 1_000_000) {\n return `${(num / 1_000_000).toFixed(1).replace(/\\.0$/, '')}M`\n }\n if (num >= 1_000) {\n return `${(num / 1_000).toFixed(1).replace(/\\.0$/, '')}K`\n }\n return num.toString()\n}\n\n/**\n * Format date to relative time with absolute on hover (Persian)\n * @example formatRelativeTime(new Date()) => '۲ ساعت پیش'\n */\nexport function formatRelativeTime(date: Date | string | number): string {\n const now = new Date()\n const then = new Date(date)\n const diffInSeconds = Math.floor((now.getTime() - then.getTime()) / 1000)\n\n if (diffInSeconds < 60) {\n return 'همین الان'\n }\n\n const diffInMinutes = Math.floor(diffInSeconds / 60)\n if (diffInMinutes < 60) {\n return `${convertToLocalNumbers(diffInMinutes, 'fa')} دقیقه پیش`\n }\n\n const diffInHours = Math.floor(diffInMinutes / 60)\n if (diffInHours < 24) {\n return `${convertToLocalNumbers(diffInHours, 'fa')} ساعت پیش`\n }\n\n const diffInDays = Math.floor(diffInHours / 24)\n if (diffInDays < 7) {\n return `${convertToLocalNumbers(diffInDays, 'fa')} روز پیش`\n }\n\n const diffInWeeks = Math.floor(diffInDays / 7)\n if (diffInWeeks < 4) {\n return `${convertToLocalNumbers(diffInWeeks, 'fa')} هفته پیش`\n }\n\n const diffInMonths = Math.floor(diffInDays / 30)\n if (diffInMonths < 12) {\n return `${convertToLocalNumbers(diffInMonths, 'fa')} ماه پیش`\n }\n\n const diffInYears = Math.floor(diffInDays / 365)\n return `${convertToLocalNumbers(diffInYears, 'fa')} سال پیش`\n}\n\n/**\n * Format date to absolute format (Persian / Jalali)\n * Uses date-fns-jalali for accurate Jalali conversion.\n * @example formatAbsoluteTime(new Date()) => '۱۵ دی ۱۴۰۳، ۱۵:۳۰'\n */\nexport function formatAbsoluteTime(date: Date | string | number): string {\n const d = new Date(date)\n return formatJalaliDate(d, 'd MMMM yyyy، HH:mm')\n}\n","'use client'\n\nimport * as React from 'react'\nimport { cva } from 'class-variance-authority'\nimport { cn } from '@/lib/utils'\n\nexport type SocialPlatform =\n | 'instagram'\n | 'twitter'\n | 'tiktok'\n | 'youtube'\n | 'linkedin'\n | 'telegram'\n | 'threads'\n | 'facebook'\n\nexport interface SocialPlatformBadgeProps extends React.HTMLAttributes<HTMLSpanElement> {\n /** The social media platform to display */\n platform: SocialPlatform\n /** Size of the badge */\n size?: 'xs' | 'sm' | 'md' | 'lg'\n /** Show the platform name label */\n showLabel?: boolean\n /** Visual style of the badge */\n variant?: 'flat' | 'badge'\n}\n\nconst PLATFORM_META: Record<\n SocialPlatform,\n { label: string; icon: React.FC<{ className?: string; style?: React.CSSProperties }> }\n> = {\n instagram: {\n label: 'Instagram',\n icon: ({ className }) => (\n <svg className={className} viewBox=\"0 0 24 24\" fill=\"currentColor\" aria-hidden=\"true\">\n <path d=\"M12 2.163c3.204 0 3.584.012 4.85.07 3.252.148 4.771 1.691 4.919 4.919.058 1.265.069 1.645.069 4.849 0 3.205-.012 3.584-.069 4.849-.149 3.225-1.664 4.771-4.919 4.919-1.266.058-1.644.07-4.85.07-3.204 0-3.584-.012-4.849-.07-3.26-.149-4.771-1.699-4.919-4.92-.058-1.265-.07-1.644-.07-4.849 0-3.204.013-3.583.07-4.849.149-3.227 1.664-4.771 4.919-4.919 1.266-.057 1.645-.069 4.849-.069zm0-2.163c-3.259 0-3.667.014-4.947.072-4.358.2-6.78 2.618-6.98 6.98-.059 1.281-.073 1.689-.073 4.948 0 3.259.014 3.668.072 4.948.2 4.358 2.618 6.78 6.98 6.98 1.281.058 1.689.072 4.948.072 3.259 0 3.668-.014 4.948-.072 4.354-.2 6.782-2.618 6.979-6.98.059-1.28.073-1.689.073-4.948 0-3.259-.014-3.667-.072-4.947-.196-4.354-2.617-6.78-6.979-6.98-1.281-.059-1.69-.073-4.949-.073zm0 5.838c-3.403 0-6.162 2.759-6.162 6.162s2.759 6.163 6.162 6.163 6.162-2.759 6.162-6.163c0-3.403-2.759-6.162-6.162-6.162zm0 10.162c-2.209 0-4-1.79-4-4 0-2.209 1.791-4 4-4s4 1.791 4 4c0 2.21-1.791 4-4 4zm6.406-11.845c-.796 0-1.441.645-1.441 1.44s.645 1.44 1.441 1.44c.795 0 1.439-.645 1.439-1.44s-.644-1.44-1.439-1.44z\" />\n </svg>\n ),\n },\n twitter: {\n label: 'X',\n icon: ({ className }) => (\n <svg className={className} viewBox=\"0 0 24 24\" fill=\"currentColor\" aria-hidden=\"true\">\n <path d=\"M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-4.714-6.231-5.401 6.231H2.744l7.737-8.835L1.254 2.25H8.08l4.259 5.63 5.905-5.63zm-1.161 17.52h1.833L7.084 4.126H5.117z\" />\n </svg>\n ),\n },\n tiktok: {\n label: 'TikTok',\n icon: ({ className }) => (\n <svg className={className} viewBox=\"0 0 24 24\" fill=\"currentColor\" aria-hidden=\"true\">\n <path d=\"M19.59 6.69a4.83 4.83 0 01-3.77-4.25V2h-3.45v13.67a2.89 2.89 0 01-2.88 2.5 2.89 2.89 0 01-2.89-2.89 2.89 2.89 0 012.89-2.89c.28 0 .54.04.79.1V9.01a6.33 6.33 0 00-.79-.05 6.34 6.34 0 00-6.34 6.34 6.34 6.34 0 006.34 6.34 6.34 6.34 0 006.33-6.34V8.69a8.16 8.16 0 004.77 1.52V6.75a4.85 4.85 0 01-1-.06z\" />\n </svg>\n ),\n },\n youtube: {\n label: 'YouTube',\n icon: ({ className }) => (\n <svg className={className} viewBox=\"0 0 24 24\" fill=\"currentColor\" aria-hidden=\"true\">\n <path d=\"M23.498 6.186a3.016 3.016 0 00-2.122-2.136C19.505 3.545 12 3.545 12 3.545s-7.505 0-9.377.505A3.017 3.017 0 00.502 6.186C0 8.07 0 12 0 12s0 3.93.502 5.814a3.016 3.016 0 002.122 2.136c1.871.505 9.376.505 9.376.505s7.505 0 9.377-.505a3.015 3.015 0 002.122-2.136C24 15.93 24 12 24 12s0-3.93-.502-5.814zM9.545 15.568V8.432L15.818 12l-6.273 3.568z\" />\n </svg>\n ),\n },\n linkedin: {\n label: 'LinkedIn',\n icon: ({ className }) => (\n <svg className={className} viewBox=\"0 0 24 24\" fill=\"currentColor\" aria-hidden=\"true\">\n <path d=\"M20.447 20.452h-3.554v-5.569c0-1.328-.027-3.037-1.852-3.037-1.853 0-2.136 1.445-2.136 2.939v5.667H9.351V9h3.414v1.561h.046c.477-.9 1.637-1.85 3.37-1.85 3.601 0 4.267 2.37 4.267 5.455v6.286zM5.337 7.433a2.062 2.062 0 01-2.063-2.065 2.064 2.064 0 112.063 2.065zm1.782 13.019H3.555V9h3.564v11.452zM22.225 0H1.771C.792 0 0 .774 0 1.729v20.542C0 23.227.792 24 1.771 24h20.451C23.2 24 24 23.227 24 22.271V1.729C24 .774 23.2 0 22.222 0h.003z\" />\n </svg>\n ),\n },\n telegram: {\n label: 'Telegram',\n icon: ({ className }) => (\n <svg className={className} viewBox=\"0 0 24 24\" fill=\"currentColor\" aria-hidden=\"true\">\n <path d=\"M11.944 0A12 12 0 000 12a12 12 0 0012 12 12 12 0 0012-12A12 12 0 0012 0a12 12 0 00-.056 0zm4.962 7.224c.1-.002.321.023.465.14a.506.506 0 01.171.325c.016.093.036.306.02.472-.18 1.898-.962 6.502-1.36 8.627-.168.9-.499 1.201-.82 1.23-.696.065-1.225-.46-1.9-.902-1.056-.693-1.653-1.124-2.678-1.8-1.185-.78-.417-1.21.258-1.91.177-.184 3.247-2.977 3.307-3.23.007-.032.014-.15-.056-.212s-.174-.041-.249-.024c-.106.024-1.793 1.14-5.061 3.345-.48.33-.913.49-1.302.48-.428-.008-1.252-.241-1.865-.44-.752-.245-1.349-.374-1.297-.789.027-.216.325-.437.893-.663 3.498-1.524 5.83-2.529 6.998-3.014 3.332-1.386 4.025-1.627 4.476-1.635z\" />\n </svg>\n ),\n },\n threads: {\n label: 'Threads',\n icon: ({ className }) => (\n <svg className={className} viewBox=\"0 0 24 24\" fill=\"currentColor\" aria-hidden=\"true\">\n <path d=\"M12.186 24h-.007c-3.581-.024-6.334-1.205-8.184-3.509C2.35 18.44 1.5 15.586 1.472 12.01v-.017c.03-3.579.879-6.43 2.525-8.482C5.868 1.205 8.617.024 12.18 0h.014c2.746.02 5.043.725 6.826 2.098 1.677 1.29 2.858 3.13 3.509 5.467l-2.04.569c-1.104-3.96-3.898-5.984-8.304-6.015-2.91.022-5.11.936-6.54 2.717C4.307 6.504 3.616 8.914 3.589 12c.027 3.086.718 5.496 2.057 7.164 1.43 1.783 3.631 2.698 6.54 2.717 2.623-.02 4.358-.631 5.8-2.045 1.647-1.613 1.618-3.593 1.09-4.798-.31-.71-.873-1.3-1.634-1.75-.192 1.352-.622 2.446-1.284 3.272-.886 1.102-2.14 1.704-3.73 1.79-1.202.065-2.361-.218-3.259-.801-1.063-.689-1.685-1.749-1.752-2.98-.065-1.19.388-2.285 1.277-3.086.84-.754 2.02-1.164 3.43-1.19.954-.018 1.858.09 2.695.322-.082-.858-.372-1.502-.861-1.903-.529-.434-1.32-.658-2.332-.67-.884.01-1.63.23-2.22.65l-1.11-1.75c.97-.625 2.18-.95 3.6-.97h.022c1.63.02 2.976.497 3.898 1.385.955.92 1.444 2.25 1.51 3.95.23.073.46.149.682.232 1.69.64 2.847 1.635 3.406 2.946.859 2.002.86 5.139-1.773 7.674-1.797 1.742-3.993 2.525-6.935 2.545zm.098-10.02c-.745.01-1.377.186-1.882.523-.443.298-.706.733-.67 1.215.03.564.342 1.017.876 1.275.516.25 1.087.299 1.58.127.684-.24 1.34-.93 1.614-2.366a9.68 9.68 0 00-1.518-.774z\" />\n </svg>\n ),\n },\n facebook: {\n label: 'Facebook',\n icon: ({ className }) => (\n <svg className={className} viewBox=\"0 0 24 24\" fill=\"currentColor\" aria-hidden=\"true\">\n <path d=\"M24 12.073c0-6.627-5.373-12-12-12s-12 5.373-12 12c0 5.99 4.388 10.954 10.125 11.854v-8.385H7.078v-3.47h3.047V9.43c0-3.007 1.792-4.669 4.533-4.669 1.312 0 2.686.235 2.686.235v2.953H15.83c-1.491 0-1.956.925-1.956 1.875v2.25h3.328l-.532 3.47h-2.796v8.385C19.612 23.027 24 18.062 24 12.073z\" />\n </svg>\n ),\n },\n}\n\nconst socialPlatformBadgeVariants = cva('inline-flex items-center font-medium transition-colors duration-150', {\n variants: {\n size: {\n xs: 'gap-1',\n sm: 'gap-1.5',\n md: 'gap-1.5',\n lg: 'gap-2',\n },\n variant: {\n flat: '',\n badge: 'border border-border bg-surface-100',\n },\n },\n compoundVariants: [\n { variant: 'badge', size: 'xs', className: 'px-1.5 py-0.5 rounded' },\n { variant: 'badge', size: 'sm', className: 'px-2 py-1 rounded' },\n { variant: 'badge', size: 'md', className: 'px-2.5 py-1 rounded-md' },\n { variant: 'badge', size: 'lg', className: 'px-3 py-1.5 rounded-md' },\n ],\n defaultVariants: { size: 'sm', variant: 'flat' },\n})\n\n/** Size-specific classes for child elements (icon, text) */\nconst SIZE_CHILD_CLASSES = {\n xs: { icon: 'size-3', text: 'text-xs' },\n sm: { icon: 'size-4', text: 'text-xs' },\n md: { icon: 'size-5', text: 'text-sm' },\n lg: { icon: 'size-6', text: 'text-base' },\n}\n\nconst SocialPlatformBadge = React.forwardRef<HTMLSpanElement, SocialPlatformBadgeProps>(\n ({ className, platform, size = 'sm', showLabel = false, variant = 'flat', style, ...props }, ref) => {\n const meta = PLATFORM_META[platform]\n const Icon = meta.icon\n const sizeClasses = SIZE_CHILD_CLASSES[size]\n\n return (\n <span\n ref={ref}\n data-slot=\"social-platform-badge\"\n className={cn(socialPlatformBadgeVariants({ size, variant }), className)}\n style={style}\n {...props}\n >\n <Icon className={cn(sizeClasses.icon, 'shrink-0')} style={{ color: `var(--social-platform-${platform})` }} />\n {showLabel && <span className={cn(sizeClasses.text, 'text-foreground-light')}>{meta.label}</span>}\n </span>\n )\n }\n)\n\nSocialPlatformBadge.displayName = 'SocialPlatformBadge'\n\nexport { SocialPlatformBadge, socialPlatformBadgeVariants }\n"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/lib/utils.ts","../../../src/components/ui/sonner.tsx"],"names":["twMerge","clsx","jsx","Sonner"],"mappings":";;;;;;;AAIO,SAAS,MAAM,MAAA,EAAsB;AAC1C,EAAA,OAAOA,qBAAA,CAAQC,SAAA,CAAK,MAAM,CAAC,CAAA;AAC7B;ACCA,IAAM,UAAU,CAAC,EAAE,SAAA,EAAW,GAAG,OAAM,KAAyB;AAC9D,EAAA,uBACEC,cAAA;AAAA,IAACC,cAAA;AAAA,IAAA;AAAA,MACC,WAAA,EAAU,SAAA;AAAA,MACV,SAAA,EAAW,EAAA,CAAG,eAAA,EAAiB,SAAS,CAAA;AAAA,MACxC,GAAA,EAAI,KAAA;AAAA,MACJ,QAAA,EAAS,YAAA;AAAA,MACT,WAAA,EAAW,IAAA;AAAA,MACX,YAAA,EAAc;AAAA,QACZ,UAAA,EAAY;AAAA,UACV,KAAA,EACE,+JAAA;AAAA,UACF,WAAA,EAAa,sCAAA;AAAA,UACb,YAAA,EAAc,kEAAA;AAAA,UACd,YAAA,EAAc,8DAAA;AAAA,UACd,OAAA,EACE,4JAAA;AAAA,UACF,KAAA,EACE,sKAAA;AAAA,UACF,OAAA,EACE,sJAAA;AAAA;AAAA;AAAA;AAAA;AAAA,UAKF,IAAA,EAAM;AAAA;AACR,OACF;AAAA,MACC,GAAG;AAAA;AAAA,GACN;AAEJ;AACA,OAAA,CAAQ,WAAA,GAAc,SAAA;AAIf,IAAM,uBAAA,GAA0B","file":"sonner.cjs","sourcesContent":["import { type ClassValue, clsx } from 'clsx'\nimport { twMerge } from 'tailwind-merge'\nimport { formatJalaliDate } from '@/lib/jalali-utils'\n\nexport function cn(...inputs: ClassValue[]) {\n return twMerge(clsx(inputs))\n}\n\nexport type SupportedLocale = 'fa' | 'ar' | 'en'\n\n/**\n * Convert digits in a string to Persian/Arabic numerals based on locale.\n * @example convertToLocalNumbers('123', 'fa') => '۱۲۳'\n * @example convertToLocalNumbers('123', 'en') => '123'\n */\nexport function convertToLocalNumbers(text: string | number, locale: SupportedLocale): string {\n if (locale === 'fa' || locale === 'ar') {\n const persianDigits = ['۰', '۱', '۲', '۳', '۴', '۵', '۶', '۷', '۸', '۹']\n return String(text).replace(/\\d/g, (digit) => persianDigits[parseInt(digit)])\n }\n return String(text)\n}\n\n/**\n * Format large numbers with locale-aware suffixes (K/M/B).\n * @example formatLargeNumber(1500, 'fa') => '۱.۵ هزار'\n * @example formatLargeNumber(1500, 'en') => '1.5K'\n */\nexport function formatLargeNumber(num: number, locale: SupportedLocale): string {\n if (num >= 1_000_000_000) {\n const formatted = (num / 1_000_000_000).toFixed(1).replace(/\\.0$/, '')\n return convertToLocalNumbers(formatted, locale) + (locale === 'en' ? 'B' : ' میلیارد')\n }\n if (num >= 1_000_000) {\n const formatted = (num / 1_000_000).toFixed(1).replace(/\\.0$/, '')\n return convertToLocalNumbers(formatted, locale) + (locale === 'en' ? 'M' : ' میلیون')\n }\n if (num >= 1_000) {\n const formatted = (num / 1_000).toFixed(1).replace(/\\.0$/, '')\n return convertToLocalNumbers(formatted, locale) + (locale === 'en' ? 'K' : ' هزار')\n }\n return convertToLocalNumbers(num.toString().replace(/\\B(?=(\\d{3})+(?!\\d))/g, ','), locale)\n}\n\n/**\n * Format number to Instagram-style short format (English only).\n * @example formatNumber(123456, 'short') => '123K'\n * @example formatNumber(123456, 'exact') => '123,456'\n */\nexport function formatNumber(num: number | undefined, format: 'exact' | 'short' = 'exact'): string {\n if (num === undefined || num === null) return '0'\n\n if (format === 'exact') {\n return num.toLocaleString('en-US')\n }\n\n // Short format (Instagram style)\n if (num >= 1_000_000_000) {\n return `${(num / 1_000_000_000).toFixed(1).replace(/\\.0$/, '')}B`\n }\n if (num >= 1_000_000) {\n return `${(num / 1_000_000).toFixed(1).replace(/\\.0$/, '')}M`\n }\n if (num >= 1_000) {\n return `${(num / 1_000).toFixed(1).replace(/\\.0$/, '')}K`\n }\n return num.toString()\n}\n\n/**\n * Format date to relative time with absolute on hover (Persian)\n * @example formatRelativeTime(new Date()) => '۲ ساعت پیش'\n */\nexport function formatRelativeTime(date: Date | string | number): string {\n const now = new Date()\n const then = new Date(date)\n const diffInSeconds = Math.floor((now.getTime() - then.getTime()) / 1000)\n\n if (diffInSeconds < 60) {\n return 'همین الان'\n }\n\n const diffInMinutes = Math.floor(diffInSeconds / 60)\n if (diffInMinutes < 60) {\n return `${convertToLocalNumbers(diffInMinutes, 'fa')} دقیقه پیش`\n }\n\n const diffInHours = Math.floor(diffInMinutes / 60)\n if (diffInHours < 24) {\n return `${convertToLocalNumbers(diffInHours, 'fa')} ساعت پیش`\n }\n\n const diffInDays = Math.floor(diffInHours / 24)\n if (diffInDays < 7) {\n return `${convertToLocalNumbers(diffInDays, 'fa')} روز پیش`\n }\n\n const diffInWeeks = Math.floor(diffInDays / 7)\n if (diffInWeeks < 4) {\n return `${convertToLocalNumbers(diffInWeeks, 'fa')} هفته پیش`\n }\n\n const diffInMonths = Math.floor(diffInDays / 30)\n if (diffInMonths < 12) {\n return `${convertToLocalNumbers(diffInMonths, 'fa')} ماه پیش`\n }\n\n const diffInYears = Math.floor(diffInDays / 365)\n return `${convertToLocalNumbers(diffInYears, 'fa')} سال پیش`\n}\n\n/**\n * Format date to absolute format (Persian / Jalali)\n * Uses date-fns-jalali for accurate Jalali conversion.\n * @example formatAbsoluteTime(new Date()) => '۱۵ دی ۱۴۰۳، ۱۵:۳۰'\n */\nexport function formatAbsoluteTime(date: Date | string | number): string {\n const d = new Date(date)\n return formatJalaliDate(d, 'd MMMM yyyy، HH:mm')\n}\n","'use client'\n\nimport { Toaster as Sonner, type ToasterProps } from 'sonner'\nimport { cn } from '@/lib/utils'\n\nexport type PartoToasterProps = ToasterProps\n\nconst Toaster = ({ className, ...props }: PartoToasterProps) => {\n return (\n <Sonner\n data-slot=\"toaster\"\n className={cn('toaster group', className)}\n dir=\"rtl\"\n position=\"top-center\"\n closeButton\n toastOptions={{\n classNames: {\n toast:\n 'group toast group-[.toaster]:bg-background group-[.toaster]:text-foreground group-[.toaster]:border-border group-[.toaster]:shadow-lg font-[var(--font-sans)]',\n description: 'group-[.toast]:text-muted-foreground',\n actionButton: 'group-[.toast]:bg-primary group-[.toast]:text-primary-foreground',\n cancelButton: 'group-[.toast]:bg-muted group-[.toast]:text-muted-foreground',\n success:\n 'group-[.toaster]:border-[hsl(var(--brand-default)/0.4)] group-[.toaster]:bg-[hsl(var(--brand-default)/0.06)] group-[.toaster]:text-[hsl(var(--brand-600))]',\n error:\n 'group-[.toaster]:border-[hsl(var(--destructive-500)/0.4)] group-[.toaster]:bg-[hsl(var(--destructive-500)/0.06)] group-[.toaster]:text-[hsl(var(--destructive-600))]',\n warning:\n 'group-[.toaster]:border-[hsl(var(--warning-500)/0.4)] group-[.toaster]:bg-[hsl(var(--warning-500)/0.06)] group-[.toaster]:text-[hsl(var(--warning))]',\n // 1.1.16 — `info` toast was pointing at --secondary-default\n // (hue 247° purple, legacy Supabase token). info has its own\n // semantic family now (--info-default, added 1.1.15), so wire\n // the toast to it.\n info: 'group-[.toaster]:border-[hsl(var(--info-default)/0.4)] group-[.toaster]:bg-[hsl(var(--info-default)/0.06)] group-[.toaster]:text-[hsl(var(--info-default))]',\n },\n }}\n {...props}\n />\n )\n}\nToaster.displayName = 'Toaster'\n\nexport { Toaster }\nexport { toast } from 'sonner'\nexport const SONNER_DEFAULT_DURATION = 4000\n"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/lib/utils.ts","../../../src/components/ui/sonner.tsx"],"names":["Sonner"],"mappings":";;;;;;AAIO,SAAS,MAAM,MAAA,EAAsB;AAC1C,EAAA,OAAO,OAAA,CAAQ,IAAA,CAAK,MAAM,CAAC,CAAA;AAC7B;ACCA,IAAM,UAAU,CAAC,EAAE,SAAA,EAAW,GAAG,OAAM,KAAyB;AAC9D,EAAA,uBACE,GAAA;AAAA,IAACA,SAAA;AAAA,IAAA;AAAA,MACC,WAAA,EAAU,SAAA;AAAA,MACV,SAAA,EAAW,EAAA,CAAG,eAAA,EAAiB,SAAS,CAAA;AAAA,MACxC,GAAA,EAAI,KAAA;AAAA,MACJ,QAAA,EAAS,YAAA;AAAA,MACT,WAAA,EAAW,IAAA;AAAA,MACX,YAAA,EAAc;AAAA,QACZ,UAAA,EAAY;AAAA,UACV,KAAA,EACE,+JAAA;AAAA,UACF,WAAA,EAAa,sCAAA;AAAA,UACb,YAAA,EAAc,kEAAA;AAAA,UACd,YAAA,EAAc,8DAAA;AAAA,UACd,OAAA,EACE,4JAAA;AAAA,UACF,KAAA,EACE,sKAAA;AAAA,UACF,OAAA,EACE,sJAAA;AAAA;AAAA;AAAA;AAAA;AAAA,UAKF,IAAA,EAAM;AAAA;AACR,OACF;AAAA,MACC,GAAG;AAAA;AAAA,GACN;AAEJ;AACA,OAAA,CAAQ,WAAA,GAAc,SAAA;AAIf,IAAM,uBAAA,GAA0B","file":"sonner.js","sourcesContent":["import { type ClassValue, clsx } from 'clsx'\nimport { twMerge } from 'tailwind-merge'\nimport { formatJalaliDate } from '@/lib/jalali-utils'\n\nexport function cn(...inputs: ClassValue[]) {\n return twMerge(clsx(inputs))\n}\n\nexport type SupportedLocale = 'fa' | 'ar' | 'en'\n\n/**\n * Convert digits in a string to Persian/Arabic numerals based on locale.\n * @example convertToLocalNumbers('123', 'fa') => '۱۲۳'\n * @example convertToLocalNumbers('123', 'en') => '123'\n */\nexport function convertToLocalNumbers(text: string | number, locale: SupportedLocale): string {\n if (locale === 'fa' || locale === 'ar') {\n const persianDigits = ['۰', '۱', '۲', '۳', '۴', '۵', '۶', '۷', '۸', '۹']\n return String(text).replace(/\\d/g, (digit) => persianDigits[parseInt(digit)])\n }\n return String(text)\n}\n\n/**\n * Format large numbers with locale-aware suffixes (K/M/B).\n * @example formatLargeNumber(1500, 'fa') => '۱.۵ هزار'\n * @example formatLargeNumber(1500, 'en') => '1.5K'\n */\nexport function formatLargeNumber(num: number, locale: SupportedLocale): string {\n if (num >= 1_000_000_000) {\n const formatted = (num / 1_000_000_000).toFixed(1).replace(/\\.0$/, '')\n return convertToLocalNumbers(formatted, locale) + (locale === 'en' ? 'B' : ' میلیارد')\n }\n if (num >= 1_000_000) {\n const formatted = (num / 1_000_000).toFixed(1).replace(/\\.0$/, '')\n return convertToLocalNumbers(formatted, locale) + (locale === 'en' ? 'M' : ' میلیون')\n }\n if (num >= 1_000) {\n const formatted = (num / 1_000).toFixed(1).replace(/\\.0$/, '')\n return convertToLocalNumbers(formatted, locale) + (locale === 'en' ? 'K' : ' هزار')\n }\n return convertToLocalNumbers(num.toString().replace(/\\B(?=(\\d{3})+(?!\\d))/g, ','), locale)\n}\n\n/**\n * Format number to Instagram-style short format (English only).\n * @example formatNumber(123456, 'short') => '123K'\n * @example formatNumber(123456, 'exact') => '123,456'\n */\nexport function formatNumber(num: number | undefined, format: 'exact' | 'short' = 'exact'): string {\n if (num === undefined || num === null) return '0'\n\n if (format === 'exact') {\n return num.toLocaleString('en-US')\n }\n\n // Short format (Instagram style)\n if (num >= 1_000_000_000) {\n return `${(num / 1_000_000_000).toFixed(1).replace(/\\.0$/, '')}B`\n }\n if (num >= 1_000_000) {\n return `${(num / 1_000_000).toFixed(1).replace(/\\.0$/, '')}M`\n }\n if (num >= 1_000) {\n return `${(num / 1_000).toFixed(1).replace(/\\.0$/, '')}K`\n }\n return num.toString()\n}\n\n/**\n * Format date to relative time with absolute on hover (Persian)\n * @example formatRelativeTime(new Date()) => '۲ ساعت پیش'\n */\nexport function formatRelativeTime(date: Date | string | number): string {\n const now = new Date()\n const then = new Date(date)\n const diffInSeconds = Math.floor((now.getTime() - then.getTime()) / 1000)\n\n if (diffInSeconds < 60) {\n return 'همین الان'\n }\n\n const diffInMinutes = Math.floor(diffInSeconds / 60)\n if (diffInMinutes < 60) {\n return `${convertToLocalNumbers(diffInMinutes, 'fa')} دقیقه پیش`\n }\n\n const diffInHours = Math.floor(diffInMinutes / 60)\n if (diffInHours < 24) {\n return `${convertToLocalNumbers(diffInHours, 'fa')} ساعت پیش`\n }\n\n const diffInDays = Math.floor(diffInHours / 24)\n if (diffInDays < 7) {\n return `${convertToLocalNumbers(diffInDays, 'fa')} روز پیش`\n }\n\n const diffInWeeks = Math.floor(diffInDays / 7)\n if (diffInWeeks < 4) {\n return `${convertToLocalNumbers(diffInWeeks, 'fa')} هفته پیش`\n }\n\n const diffInMonths = Math.floor(diffInDays / 30)\n if (diffInMonths < 12) {\n return `${convertToLocalNumbers(diffInMonths, 'fa')} ماه پیش`\n }\n\n const diffInYears = Math.floor(diffInDays / 365)\n return `${convertToLocalNumbers(diffInYears, 'fa')} سال پیش`\n}\n\n/**\n * Format date to absolute format (Persian / Jalali)\n * Uses date-fns-jalali for accurate Jalali conversion.\n * @example formatAbsoluteTime(new Date()) => '۱۵ دی ۱۴۰۳، ۱۵:۳۰'\n */\nexport function formatAbsoluteTime(date: Date | string | number): string {\n const d = new Date(date)\n return formatJalaliDate(d, 'd MMMM yyyy، HH:mm')\n}\n","'use client'\n\nimport { Toaster as Sonner, type ToasterProps } from 'sonner'\nimport { cn } from '@/lib/utils'\n\nexport type PartoToasterProps = ToasterProps\n\nconst Toaster = ({ className, ...props }: PartoToasterProps) => {\n return (\n <Sonner\n data-slot=\"toaster\"\n className={cn('toaster group', className)}\n dir=\"rtl\"\n position=\"top-center\"\n closeButton\n toastOptions={{\n classNames: {\n toast:\n 'group toast group-[.toaster]:bg-background group-[.toaster]:text-foreground group-[.toaster]:border-border group-[.toaster]:shadow-lg font-[var(--font-sans)]',\n description: 'group-[.toast]:text-muted-foreground',\n actionButton: 'group-[.toast]:bg-primary group-[.toast]:text-primary-foreground',\n cancelButton: 'group-[.toast]:bg-muted group-[.toast]:text-muted-foreground',\n success:\n 'group-[.toaster]:border-[hsl(var(--brand-default)/0.4)] group-[.toaster]:bg-[hsl(var(--brand-default)/0.06)] group-[.toaster]:text-[hsl(var(--brand-600))]',\n error:\n 'group-[.toaster]:border-[hsl(var(--destructive-500)/0.4)] group-[.toaster]:bg-[hsl(var(--destructive-500)/0.06)] group-[.toaster]:text-[hsl(var(--destructive-600))]',\n warning:\n 'group-[.toaster]:border-[hsl(var(--warning-500)/0.4)] group-[.toaster]:bg-[hsl(var(--warning-500)/0.06)] group-[.toaster]:text-[hsl(var(--warning))]',\n // 1.1.16 — `info` toast was pointing at --secondary-default\n // (hue 247° purple, legacy Supabase token). info has its own\n // semantic family now (--info-default, added 1.1.15), so wire\n // the toast to it.\n info: 'group-[.toaster]:border-[hsl(var(--info-default)/0.4)] group-[.toaster]:bg-[hsl(var(--info-default)/0.06)] group-[.toaster]:text-[hsl(var(--info-default))]',\n },\n }}\n {...props}\n />\n )\n}\nToaster.displayName = 'Toaster'\n\nexport { Toaster }\nexport { toast } from 'sonner'\nexport const SONNER_DEFAULT_DURATION = 4000\n"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/lib/utils.ts","../../../src/components/ui/sparkline.tsx"],"names":["twMerge","clsx","React","jsx","jsxs"],"mappings":"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","file":"sparkline.cjs","sourcesContent":["import { type ClassValue, clsx } from 'clsx'\nimport { twMerge } from 'tailwind-merge'\nimport { formatJalaliDate } from '@/lib/jalali-utils'\n\nexport function cn(...inputs: ClassValue[]) {\n return twMerge(clsx(inputs))\n}\n\nexport type SupportedLocale = 'fa' | 'ar' | 'en'\n\n/**\n * Convert digits in a string to Persian/Arabic numerals based on locale.\n * @example convertToLocalNumbers('123', 'fa') => '۱۲۳'\n * @example convertToLocalNumbers('123', 'en') => '123'\n */\nexport function convertToLocalNumbers(text: string | number, locale: SupportedLocale): string {\n if (locale === 'fa' || locale === 'ar') {\n const persianDigits = ['۰', '۱', '۲', '۳', '۴', '۵', '۶', '۷', '۸', '۹']\n return String(text).replace(/\\d/g, (digit) => persianDigits[parseInt(digit)])\n }\n return String(text)\n}\n\n/**\n * Format large numbers with locale-aware suffixes (K/M/B).\n * @example formatLargeNumber(1500, 'fa') => '۱.۵ هزار'\n * @example formatLargeNumber(1500, 'en') => '1.5K'\n */\nexport function formatLargeNumber(num: number, locale: SupportedLocale): string {\n if (num >= 1_000_000_000) {\n const formatted = (num / 1_000_000_000).toFixed(1).replace(/\\.0$/, '')\n return convertToLocalNumbers(formatted, locale) + (locale === 'en' ? 'B' : ' میلیارد')\n }\n if (num >= 1_000_000) {\n const formatted = (num / 1_000_000).toFixed(1).replace(/\\.0$/, '')\n return convertToLocalNumbers(formatted, locale) + (locale === 'en' ? 'M' : ' میلیون')\n }\n if (num >= 1_000) {\n const formatted = (num / 1_000).toFixed(1).replace(/\\.0$/, '')\n return convertToLocalNumbers(formatted, locale) + (locale === 'en' ? 'K' : ' هزار')\n }\n return convertToLocalNumbers(num.toString().replace(/\\B(?=(\\d{3})+(?!\\d))/g, ','), locale)\n}\n\n/**\n * Format number to Instagram-style short format (English only).\n * @example formatNumber(123456, 'short') => '123K'\n * @example formatNumber(123456, 'exact') => '123,456'\n */\nexport function formatNumber(num: number | undefined, format: 'exact' | 'short' = 'exact'): string {\n if (num === undefined || num === null) return '0'\n\n if (format === 'exact') {\n return num.toLocaleString('en-US')\n }\n\n // Short format (Instagram style)\n if (num >= 1_000_000_000) {\n return `${(num / 1_000_000_000).toFixed(1).replace(/\\.0$/, '')}B`\n }\n if (num >= 1_000_000) {\n return `${(num / 1_000_000).toFixed(1).replace(/\\.0$/, '')}M`\n }\n if (num >= 1_000) {\n return `${(num / 1_000).toFixed(1).replace(/\\.0$/, '')}K`\n }\n return num.toString()\n}\n\n/**\n * Format date to relative time with absolute on hover (Persian)\n * @example formatRelativeTime(new Date()) => '۲ ساعت پیش'\n */\nexport function formatRelativeTime(date: Date | string | number): string {\n const now = new Date()\n const then = new Date(date)\n const diffInSeconds = Math.floor((now.getTime() - then.getTime()) / 1000)\n\n if (diffInSeconds < 60) {\n return 'همین الان'\n }\n\n const diffInMinutes = Math.floor(diffInSeconds / 60)\n if (diffInMinutes < 60) {\n return `${convertToLocalNumbers(diffInMinutes, 'fa')} دقیقه پیش`\n }\n\n const diffInHours = Math.floor(diffInMinutes / 60)\n if (diffInHours < 24) {\n return `${convertToLocalNumbers(diffInHours, 'fa')} ساعت پیش`\n }\n\n const diffInDays = Math.floor(diffInHours / 24)\n if (diffInDays < 7) {\n return `${convertToLocalNumbers(diffInDays, 'fa')} روز پیش`\n }\n\n const diffInWeeks = Math.floor(diffInDays / 7)\n if (diffInWeeks < 4) {\n return `${convertToLocalNumbers(diffInWeeks, 'fa')} هفته پیش`\n }\n\n const diffInMonths = Math.floor(diffInDays / 30)\n if (diffInMonths < 12) {\n return `${convertToLocalNumbers(diffInMonths, 'fa')} ماه پیش`\n }\n\n const diffInYears = Math.floor(diffInDays / 365)\n return `${convertToLocalNumbers(diffInYears, 'fa')} سال پیش`\n}\n\n/**\n * Format date to absolute format (Persian / Jalali)\n * Uses date-fns-jalali for accurate Jalali conversion.\n * @example formatAbsoluteTime(new Date()) => '۱۵ دی ۱۴۰۳، ۱۵:۳۰'\n */\nexport function formatAbsoluteTime(date: Date | string | number): string {\n const d = new Date(date)\n return formatJalaliDate(d, 'd MMMM yyyy، HH:mm')\n}\n","'use client'\n\nimport * as React from 'react'\nimport { cn } from '@/lib/utils'\n\nexport type SparklineTrend = 'up' | 'down' | 'neutral' | 'auto'\nexport type SparklineVariant = 'line' | 'area' | 'bars'\n\nexport interface SparklineProps extends React.HTMLAttributes<HTMLDivElement> {\n /** Array of numeric data points. Must have at least 2 items to render line/area. */\n data: number[]\n /** Visual style @default \"line\" */\n variant?: SparklineVariant\n /**\n * Trend colour. `\"auto\"` compares first and last point:\n * last > first → up, last < first → down, equal → neutral.\n * Pass an explicit value to override.\n * @default \"auto\"\n */\n trend?: SparklineTrend\n /** Width in px. Also used as SVG viewBox width — output scales to parent when CSS overrides. @default 80 */\n width?: number\n /** Height in px @default 24 */\n height?: number\n /** Line / bar stroke width @default 1.5 */\n strokeWidth?: number\n /** Show a filled dot at the last point */\n showEndDot?: boolean\n /** Padding inside the viewBox so strokes/dots are not clipped @default 2 */\n padding?: number\n /** Override color with any CSS value (e.g. `\"hsl(var(--brand))\"`). Takes precedence over `trend`. */\n color?: string\n /** For bars variant — gap between bars in user units @default 1 */\n barGap?: number\n /** aria-label for screen readers — describe the trend / metric */\n 'aria-label'?: string\n}\n\nconst TREND_CLASSES: Record<Exclude<SparklineTrend, 'auto'>, string> = {\n up: 'text-[hsl(var(--sentiment-positive))]',\n down: 'text-[hsl(var(--sentiment-negative))]',\n neutral: 'text-[hsl(var(--sentiment-neutral))]',\n}\n\nfunction resolveTrend(data: number[], trend: SparklineTrend): Exclude<SparklineTrend, 'auto'> {\n if (trend !== 'auto') return trend\n if (data.length < 2) return 'neutral'\n const first = data[0]\n const last = data[data.length - 1]\n if (last > first) return 'up'\n if (last < first) return 'down'\n return 'neutral'\n}\n\nconst Sparkline = React.forwardRef<HTMLDivElement, SparklineProps>(\n (\n {\n className,\n data,\n variant = 'line',\n trend = 'auto',\n width = 80,\n height = 24,\n strokeWidth = 1.5,\n showEndDot = false,\n padding = 2,\n color,\n barGap = 1,\n 'aria-label': ariaLabel,\n ...props\n },\n ref\n ) => {\n const effectiveTrend = resolveTrend(data, trend)\n const trendColorClass = color ? undefined : TREND_CLASSES[effectiveTrend]\n\n if (data.length === 0) {\n return (\n <div\n ref={ref}\n data-slot=\"sparkline\"\n data-empty=\"true\"\n aria-hidden=\"true\"\n className={cn('inline-block', className)}\n style={{ width, height }}\n {...props}\n />\n )\n }\n\n const min = Math.min(...data)\n const max = Math.max(...data)\n const range = max - min || 1\n const innerW = width - padding * 2\n const innerH = height - padding * 2\n const stepX = data.length > 1 ? innerW / (data.length - 1) : 0\n const yFor = (v: number) => padding + innerH - ((v - min) / range) * innerH\n const xFor = (i: number) => padding + i * stepX\n\n const points = data.map((v, i) => ({ x: xFor(i), y: yFor(v) }))\n const linePath = points.map((p, i) => `${i === 0 ? 'M' : 'L'}${p.x.toFixed(2)} ${p.y.toFixed(2)}`).join(' ')\n const areaPath = `${linePath} L${points[points.length - 1].x.toFixed(2)} ${(height - padding).toFixed(\n 2\n )} L${points[0].x.toFixed(2)} ${(height - padding).toFixed(2)} Z`\n\n const stroke = color ?? 'currentColor'\n\n return (\n <div\n ref={ref}\n data-slot=\"sparkline\"\n data-variant={variant}\n data-trend={effectiveTrend}\n role={ariaLabel ? 'img' : undefined}\n aria-label={ariaLabel}\n aria-hidden={ariaLabel ? undefined : true}\n className={cn('inline-block align-middle', trendColorClass, className)}\n style={{ width, height }}\n {...props}\n >\n <svg\n width={width}\n height={height}\n viewBox={`0 0 ${width} ${height}`}\n preserveAspectRatio=\"none\"\n focusable=\"false\"\n >\n {variant === 'area' && <path d={areaPath} fill={stroke} opacity={0.18} />}\n {(variant === 'line' || variant === 'area') && (\n <path\n d={linePath}\n fill=\"none\"\n stroke={stroke}\n strokeWidth={strokeWidth}\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n vectorEffect=\"non-scaling-stroke\"\n />\n )}\n {variant === 'bars' &&\n data.map((v, i) => {\n const gap = barGap\n const barW = Math.max(0, stepX - gap)\n const y = yFor(v)\n const barH = Math.max(1, height - padding - y)\n const x = xFor(i) - barW / 2\n return (\n <rect\n key={i}\n x={x.toFixed(2)}\n y={y.toFixed(2)}\n width={barW.toFixed(2)}\n height={barH.toFixed(2)}\n rx={Math.min(1, barW / 3)}\n fill={stroke}\n />\n )\n })}\n {showEndDot && variant !== 'bars' && (\n <circle\n cx={points[points.length - 1].x.toFixed(2)}\n cy={points[points.length - 1].y.toFixed(2)}\n r={strokeWidth + 0.5}\n fill={stroke}\n />\n )}\n </svg>\n </div>\n )\n }\n)\nSparkline.displayName = 'Sparkline'\n\nexport { Sparkline }\n"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/lib/utils.ts","../../../src/components/ui/sparkline.tsx"],"names":[],"mappings":"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","file":"sparkline.js","sourcesContent":["import { type ClassValue, clsx } from 'clsx'\nimport { twMerge } from 'tailwind-merge'\nimport { formatJalaliDate } from '@/lib/jalali-utils'\n\nexport function cn(...inputs: ClassValue[]) {\n return twMerge(clsx(inputs))\n}\n\nexport type SupportedLocale = 'fa' | 'ar' | 'en'\n\n/**\n * Convert digits in a string to Persian/Arabic numerals based on locale.\n * @example convertToLocalNumbers('123', 'fa') => '۱۲۳'\n * @example convertToLocalNumbers('123', 'en') => '123'\n */\nexport function convertToLocalNumbers(text: string | number, locale: SupportedLocale): string {\n if (locale === 'fa' || locale === 'ar') {\n const persianDigits = ['۰', '۱', '۲', '۳', '۴', '۵', '۶', '۷', '۸', '۹']\n return String(text).replace(/\\d/g, (digit) => persianDigits[parseInt(digit)])\n }\n return String(text)\n}\n\n/**\n * Format large numbers with locale-aware suffixes (K/M/B).\n * @example formatLargeNumber(1500, 'fa') => '۱.۵ هزار'\n * @example formatLargeNumber(1500, 'en') => '1.5K'\n */\nexport function formatLargeNumber(num: number, locale: SupportedLocale): string {\n if (num >= 1_000_000_000) {\n const formatted = (num / 1_000_000_000).toFixed(1).replace(/\\.0$/, '')\n return convertToLocalNumbers(formatted, locale) + (locale === 'en' ? 'B' : ' میلیارد')\n }\n if (num >= 1_000_000) {\n const formatted = (num / 1_000_000).toFixed(1).replace(/\\.0$/, '')\n return convertToLocalNumbers(formatted, locale) + (locale === 'en' ? 'M' : ' میلیون')\n }\n if (num >= 1_000) {\n const formatted = (num / 1_000).toFixed(1).replace(/\\.0$/, '')\n return convertToLocalNumbers(formatted, locale) + (locale === 'en' ? 'K' : ' هزار')\n }\n return convertToLocalNumbers(num.toString().replace(/\\B(?=(\\d{3})+(?!\\d))/g, ','), locale)\n}\n\n/**\n * Format number to Instagram-style short format (English only).\n * @example formatNumber(123456, 'short') => '123K'\n * @example formatNumber(123456, 'exact') => '123,456'\n */\nexport function formatNumber(num: number | undefined, format: 'exact' | 'short' = 'exact'): string {\n if (num === undefined || num === null) return '0'\n\n if (format === 'exact') {\n return num.toLocaleString('en-US')\n }\n\n // Short format (Instagram style)\n if (num >= 1_000_000_000) {\n return `${(num / 1_000_000_000).toFixed(1).replace(/\\.0$/, '')}B`\n }\n if (num >= 1_000_000) {\n return `${(num / 1_000_000).toFixed(1).replace(/\\.0$/, '')}M`\n }\n if (num >= 1_000) {\n return `${(num / 1_000).toFixed(1).replace(/\\.0$/, '')}K`\n }\n return num.toString()\n}\n\n/**\n * Format date to relative time with absolute on hover (Persian)\n * @example formatRelativeTime(new Date()) => '۲ ساعت پیش'\n */\nexport function formatRelativeTime(date: Date | string | number): string {\n const now = new Date()\n const then = new Date(date)\n const diffInSeconds = Math.floor((now.getTime() - then.getTime()) / 1000)\n\n if (diffInSeconds < 60) {\n return 'همین الان'\n }\n\n const diffInMinutes = Math.floor(diffInSeconds / 60)\n if (diffInMinutes < 60) {\n return `${convertToLocalNumbers(diffInMinutes, 'fa')} دقیقه پیش`\n }\n\n const diffInHours = Math.floor(diffInMinutes / 60)\n if (diffInHours < 24) {\n return `${convertToLocalNumbers(diffInHours, 'fa')} ساعت پیش`\n }\n\n const diffInDays = Math.floor(diffInHours / 24)\n if (diffInDays < 7) {\n return `${convertToLocalNumbers(diffInDays, 'fa')} روز پیش`\n }\n\n const diffInWeeks = Math.floor(diffInDays / 7)\n if (diffInWeeks < 4) {\n return `${convertToLocalNumbers(diffInWeeks, 'fa')} هفته پیش`\n }\n\n const diffInMonths = Math.floor(diffInDays / 30)\n if (diffInMonths < 12) {\n return `${convertToLocalNumbers(diffInMonths, 'fa')} ماه پیش`\n }\n\n const diffInYears = Math.floor(diffInDays / 365)\n return `${convertToLocalNumbers(diffInYears, 'fa')} سال پیش`\n}\n\n/**\n * Format date to absolute format (Persian / Jalali)\n * Uses date-fns-jalali for accurate Jalali conversion.\n * @example formatAbsoluteTime(new Date()) => '۱۵ دی ۱۴۰۳، ۱۵:۳۰'\n */\nexport function formatAbsoluteTime(date: Date | string | number): string {\n const d = new Date(date)\n return formatJalaliDate(d, 'd MMMM yyyy، HH:mm')\n}\n","'use client'\n\nimport * as React from 'react'\nimport { cn } from '@/lib/utils'\n\nexport type SparklineTrend = 'up' | 'down' | 'neutral' | 'auto'\nexport type SparklineVariant = 'line' | 'area' | 'bars'\n\nexport interface SparklineProps extends React.HTMLAttributes<HTMLDivElement> {\n /** Array of numeric data points. Must have at least 2 items to render line/area. */\n data: number[]\n /** Visual style @default \"line\" */\n variant?: SparklineVariant\n /**\n * Trend colour. `\"auto\"` compares first and last point:\n * last > first → up, last < first → down, equal → neutral.\n * Pass an explicit value to override.\n * @default \"auto\"\n */\n trend?: SparklineTrend\n /** Width in px. Also used as SVG viewBox width — output scales to parent when CSS overrides. @default 80 */\n width?: number\n /** Height in px @default 24 */\n height?: number\n /** Line / bar stroke width @default 1.5 */\n strokeWidth?: number\n /** Show a filled dot at the last point */\n showEndDot?: boolean\n /** Padding inside the viewBox so strokes/dots are not clipped @default 2 */\n padding?: number\n /** Override color with any CSS value (e.g. `\"hsl(var(--brand))\"`). Takes precedence over `trend`. */\n color?: string\n /** For bars variant — gap between bars in user units @default 1 */\n barGap?: number\n /** aria-label for screen readers — describe the trend / metric */\n 'aria-label'?: string\n}\n\nconst TREND_CLASSES: Record<Exclude<SparklineTrend, 'auto'>, string> = {\n up: 'text-[hsl(var(--sentiment-positive))]',\n down: 'text-[hsl(var(--sentiment-negative))]',\n neutral: 'text-[hsl(var(--sentiment-neutral))]',\n}\n\nfunction resolveTrend(data: number[], trend: SparklineTrend): Exclude<SparklineTrend, 'auto'> {\n if (trend !== 'auto') return trend\n if (data.length < 2) return 'neutral'\n const first = data[0]\n const last = data[data.length - 1]\n if (last > first) return 'up'\n if (last < first) return 'down'\n return 'neutral'\n}\n\nconst Sparkline = React.forwardRef<HTMLDivElement, SparklineProps>(\n (\n {\n className,\n data,\n variant = 'line',\n trend = 'auto',\n width = 80,\n height = 24,\n strokeWidth = 1.5,\n showEndDot = false,\n padding = 2,\n color,\n barGap = 1,\n 'aria-label': ariaLabel,\n ...props\n },\n ref\n ) => {\n const effectiveTrend = resolveTrend(data, trend)\n const trendColorClass = color ? undefined : TREND_CLASSES[effectiveTrend]\n\n if (data.length === 0) {\n return (\n <div\n ref={ref}\n data-slot=\"sparkline\"\n data-empty=\"true\"\n aria-hidden=\"true\"\n className={cn('inline-block', className)}\n style={{ width, height }}\n {...props}\n />\n )\n }\n\n const min = Math.min(...data)\n const max = Math.max(...data)\n const range = max - min || 1\n const innerW = width - padding * 2\n const innerH = height - padding * 2\n const stepX = data.length > 1 ? innerW / (data.length - 1) : 0\n const yFor = (v: number) => padding + innerH - ((v - min) / range) * innerH\n const xFor = (i: number) => padding + i * stepX\n\n const points = data.map((v, i) => ({ x: xFor(i), y: yFor(v) }))\n const linePath = points.map((p, i) => `${i === 0 ? 'M' : 'L'}${p.x.toFixed(2)} ${p.y.toFixed(2)}`).join(' ')\n const areaPath = `${linePath} L${points[points.length - 1].x.toFixed(2)} ${(height - padding).toFixed(\n 2\n )} L${points[0].x.toFixed(2)} ${(height - padding).toFixed(2)} Z`\n\n const stroke = color ?? 'currentColor'\n\n return (\n <div\n ref={ref}\n data-slot=\"sparkline\"\n data-variant={variant}\n data-trend={effectiveTrend}\n role={ariaLabel ? 'img' : undefined}\n aria-label={ariaLabel}\n aria-hidden={ariaLabel ? undefined : true}\n className={cn('inline-block align-middle', trendColorClass, className)}\n style={{ width, height }}\n {...props}\n >\n <svg\n width={width}\n height={height}\n viewBox={`0 0 ${width} ${height}`}\n preserveAspectRatio=\"none\"\n focusable=\"false\"\n >\n {variant === 'area' && <path d={areaPath} fill={stroke} opacity={0.18} />}\n {(variant === 'line' || variant === 'area') && (\n <path\n d={linePath}\n fill=\"none\"\n stroke={stroke}\n strokeWidth={strokeWidth}\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n vectorEffect=\"non-scaling-stroke\"\n />\n )}\n {variant === 'bars' &&\n data.map((v, i) => {\n const gap = barGap\n const barW = Math.max(0, stepX - gap)\n const y = yFor(v)\n const barH = Math.max(1, height - padding - y)\n const x = xFor(i) - barW / 2\n return (\n <rect\n key={i}\n x={x.toFixed(2)}\n y={y.toFixed(2)}\n width={barW.toFixed(2)}\n height={barH.toFixed(2)}\n rx={Math.min(1, barW / 3)}\n fill={stroke}\n />\n )\n })}\n {showEndDot && variant !== 'bars' && (\n <circle\n cx={points[points.length - 1].x.toFixed(2)}\n cy={points[points.length - 1].y.toFixed(2)}\n r={strokeWidth + 0.5}\n fill={stroke}\n />\n )}\n </svg>\n </div>\n )\n }\n)\nSparkline.displayName = 'Sparkline'\n\nexport { Sparkline }\n"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/lib/utils.ts","../../../src/components/ui/switch.tsx"],"names":["twMerge","clsx","cva","React","jsx","SwitchPrimitive"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAIO,SAAS,MAAM,MAAA,EAAsB;AAC1C,EAAA,OAAOA,qBAAA,CAAQC,SAAA,CAAK,MAAM,CAAC,CAAA;AAC7B;ACEA,IAAM,kBAAA,GAAqBC,0BAAA;AAAA;AAAA;AAAA;AAAA,EAIzB,wdAAA;AAAA,EACA;AAAA,IACE,QAAA,EAAU;AAAA,MACR,IAAA,EAAM;AAAA,QACJ,EAAA,EAAI,mBAAA;AAAA,QACJ,EAAA,EAAI,mBAAA;AAAA,QACJ,EAAA,EAAI,mBAAA;AAAA;AAAA,QACsB,KAAA,EAAO,mBAAA;AAAA;AAAA,QACP,MAAA,EAAQ,mBAAA;AAAA;AAAA,QACR,KAAA,EAAO;AAAA;AACnC,KACF;AAAA,IACA,eAAA,EAAiB;AAAA,MACf,IAAA,EAAM;AAAA;AACR;AAEJ,CAAA;AAKA,IAAM,mBAAA,GAAsBA,0BAAA;AAAA,EAC1B,qIAAA;AAAA,EACA;AAAA,IACE,QAAA,EAAU;AAAA,MACR,IAAA,EAAM;AAAA,QACJ,EAAA,EAAI,QAAA;AAAA,QACJ,EAAA,EAAI,QAAA;AAAA,QACJ,EAAA,EAAI,aAAA;AAAA;AAAA,QACsB,KAAA,EAAO,QAAA;AAAA;AAAA,QACP,MAAA,EAAQ,QAAA;AAAA;AAAA,QACR,KAAA,EAAO;AAAA;AACnC,KACF;AAAA,IACA,eAAA,EAAiB;AAAA,MACf,IAAA,EAAM;AAAA;AACR;AAEJ,CAAA;AAKA,IAAM,MAAA,GAAeC,gBAAA,CAAA,UAAA;AAAA,EACnB,CAAC,EAAE,SAAA,EAAW,MAAM,GAAG,KAAA,IAAS,GAAA,qBAC9BC,cAAA;AAAA,IAAiBC,0BAAA,CAAA,IAAA;AAAA,IAAhB;AAAA,MACC,WAAA,EAAU,QAAA;AAAA,MACV,WAAW,EAAA,CAAG,kBAAA,CAAmB,EAAE,IAAA,EAAM,GAAG,SAAS,CAAA;AAAA,MACpD,GAAG,KAAA;AAAA,MACJ,GAAA;AAAA,MAEA,QAAA,kBAAAD,cAAA,CAAiBC,0BAAA,CAAA,KAAA,EAAhB,EAAsB,WAAA,EAAU,cAAA,EAAe,SAAA,EAAW,EAAA,CAAG,mBAAA,CAAoB,EAAE,IAAA,EAAM,CAAC,CAAA,EAAG;AAAA;AAAA;AAGpG;AACA,MAAA,CAAO,cAA8BA,0BAAA,CAAA,IAAA,CAAK,WAAA","file":"switch.cjs","sourcesContent":["import { type ClassValue, clsx } from 'clsx'\nimport { twMerge } from 'tailwind-merge'\nimport { formatJalaliDate } from '@/lib/jalali-utils'\n\nexport function cn(...inputs: ClassValue[]) {\n return twMerge(clsx(inputs))\n}\n\nexport type SupportedLocale = 'fa' | 'ar' | 'en'\n\n/**\n * Convert digits in a string to Persian/Arabic numerals based on locale.\n * @example convertToLocalNumbers('123', 'fa') => '۱۲۳'\n * @example convertToLocalNumbers('123', 'en') => '123'\n */\nexport function convertToLocalNumbers(text: string | number, locale: SupportedLocale): string {\n if (locale === 'fa' || locale === 'ar') {\n const persianDigits = ['۰', '۱', '۲', '۳', '۴', '۵', '۶', '۷', '۸', '۹']\n return String(text).replace(/\\d/g, (digit) => persianDigits[parseInt(digit)])\n }\n return String(text)\n}\n\n/**\n * Format large numbers with locale-aware suffixes (K/M/B).\n * @example formatLargeNumber(1500, 'fa') => '۱.۵ هزار'\n * @example formatLargeNumber(1500, 'en') => '1.5K'\n */\nexport function formatLargeNumber(num: number, locale: SupportedLocale): string {\n if (num >= 1_000_000_000) {\n const formatted = (num / 1_000_000_000).toFixed(1).replace(/\\.0$/, '')\n return convertToLocalNumbers(formatted, locale) + (locale === 'en' ? 'B' : ' میلیارد')\n }\n if (num >= 1_000_000) {\n const formatted = (num / 1_000_000).toFixed(1).replace(/\\.0$/, '')\n return convertToLocalNumbers(formatted, locale) + (locale === 'en' ? 'M' : ' میلیون')\n }\n if (num >= 1_000) {\n const formatted = (num / 1_000).toFixed(1).replace(/\\.0$/, '')\n return convertToLocalNumbers(formatted, locale) + (locale === 'en' ? 'K' : ' هزار')\n }\n return convertToLocalNumbers(num.toString().replace(/\\B(?=(\\d{3})+(?!\\d))/g, ','), locale)\n}\n\n/**\n * Format number to Instagram-style short format (English only).\n * @example formatNumber(123456, 'short') => '123K'\n * @example formatNumber(123456, 'exact') => '123,456'\n */\nexport function formatNumber(num: number | undefined, format: 'exact' | 'short' = 'exact'): string {\n if (num === undefined || num === null) return '0'\n\n if (format === 'exact') {\n return num.toLocaleString('en-US')\n }\n\n // Short format (Instagram style)\n if (num >= 1_000_000_000) {\n return `${(num / 1_000_000_000).toFixed(1).replace(/\\.0$/, '')}B`\n }\n if (num >= 1_000_000) {\n return `${(num / 1_000_000).toFixed(1).replace(/\\.0$/, '')}M`\n }\n if (num >= 1_000) {\n return `${(num / 1_000).toFixed(1).replace(/\\.0$/, '')}K`\n }\n return num.toString()\n}\n\n/**\n * Format date to relative time with absolute on hover (Persian)\n * @example formatRelativeTime(new Date()) => '۲ ساعت پیش'\n */\nexport function formatRelativeTime(date: Date | string | number): string {\n const now = new Date()\n const then = new Date(date)\n const diffInSeconds = Math.floor((now.getTime() - then.getTime()) / 1000)\n\n if (diffInSeconds < 60) {\n return 'همین الان'\n }\n\n const diffInMinutes = Math.floor(diffInSeconds / 60)\n if (diffInMinutes < 60) {\n return `${convertToLocalNumbers(diffInMinutes, 'fa')} دقیقه پیش`\n }\n\n const diffInHours = Math.floor(diffInMinutes / 60)\n if (diffInHours < 24) {\n return `${convertToLocalNumbers(diffInHours, 'fa')} ساعت پیش`\n }\n\n const diffInDays = Math.floor(diffInHours / 24)\n if (diffInDays < 7) {\n return `${convertToLocalNumbers(diffInDays, 'fa')} روز پیش`\n }\n\n const diffInWeeks = Math.floor(diffInDays / 7)\n if (diffInWeeks < 4) {\n return `${convertToLocalNumbers(diffInWeeks, 'fa')} هفته پیش`\n }\n\n const diffInMonths = Math.floor(diffInDays / 30)\n if (diffInMonths < 12) {\n return `${convertToLocalNumbers(diffInMonths, 'fa')} ماه پیش`\n }\n\n const diffInYears = Math.floor(diffInDays / 365)\n return `${convertToLocalNumbers(diffInYears, 'fa')} سال پیش`\n}\n\n/**\n * Format date to absolute format (Persian / Jalali)\n * Uses date-fns-jalali for accurate Jalali conversion.\n * @example formatAbsoluteTime(new Date()) => '۱۵ دی ۱۴۰۳، ۱۵:۳۰'\n */\nexport function formatAbsoluteTime(date: Date | string | number): string {\n const d = new Date(date)\n return formatJalaliDate(d, 'd MMMM yyyy، HH:mm')\n}\n","'use client'\n\nimport * as React from 'react'\nimport * as SwitchPrimitive from '@radix-ui/react-switch'\nimport { VariantProps, cva } from 'class-variance-authority'\n\nimport { cn } from '@/lib/utils'\n\nconst switchRootVariants = cva(\n // Hover-checked uses brand-600 in light (darker green) and brand-500 in dark (also darker\n // since --brand-* scale inverts across themes). Without the dark variant the track would get\n // *lighter* on hover in dark mode and the white thumb would lose contrast.\n 'peer inline-flex shrink-0 cursor-pointer items-center rounded-full border transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-brand data-[state=checked]:hover:bg-brand-600/90 dark:data-[state=checked]:hover:bg-brand-500/90 data-[state=unchecked]:bg-control data-[state=unchecked]:hover:bg-border',\n {\n variants: {\n size: {\n sm: 'h-[16px] w-[28px]',\n md: 'h-[20px] w-[34px]',\n lg: 'h-[24px] w-[44px]',\n /** @deprecated Use sm */ small: 'h-[16px] w-[28px]',\n /** @deprecated Use md */ medium: 'h-[20px] w-[34px]',\n /** @deprecated Use lg */ large: 'h-[24px] w-[44px]',\n },\n },\n defaultVariants: {\n size: 'sm',\n },\n }\n)\n\n// Checked thumb uses --on-brand (white in both themes) so the thumb stays bright on the\n// brand-colored track regardless of theme. Unchecked thumb uses foreground-lighter for a\n// subdued gray on the muted track.\nconst switchThumbVariants = cva(\n 'pointer-events-none block rounded-full bg-foreground-lighter data-[state=checked]:bg-on-brand shadow-lg ring-0 transition-transform',\n {\n variants: {\n size: {\n sm: 'size-3',\n md: 'size-4',\n lg: 'size-[18px]',\n /** @deprecated Use sm */ small: 'size-3',\n /** @deprecated Use md */ medium: 'size-4',\n /** @deprecated Use lg */ large: 'size-[18px]',\n },\n },\n defaultVariants: {\n size: 'sm',\n },\n }\n)\n\nexport interface SwitchProps\n extends React.ComponentPropsWithoutRef<typeof SwitchPrimitive.Root>, VariantProps<typeof switchRootVariants> {}\n\nconst Switch = React.forwardRef<React.ElementRef<typeof SwitchPrimitive.Root>, SwitchProps>(\n ({ className, size, ...props }, ref) => (\n <SwitchPrimitive.Root\n data-slot=\"switch\"\n className={cn(switchRootVariants({ size }), className)}\n {...props}\n ref={ref}\n >\n <SwitchPrimitive.Thumb data-slot=\"switch-thumb\" className={cn(switchThumbVariants({ size }))} />\n </SwitchPrimitive.Root>\n )\n)\nSwitch.displayName = SwitchPrimitive.Root.displayName\n\nexport { Switch }\n"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/lib/utils.ts","../../../src/components/ui/switch.tsx"],"names":[],"mappings":";;;;;;;AAIO,SAAS,MAAM,MAAA,EAAsB;AAC1C,EAAA,OAAO,OAAA,CAAQ,IAAA,CAAK,MAAM,CAAC,CAAA;AAC7B;ACEA,IAAM,kBAAA,GAAqB,GAAA;AAAA;AAAA;AAAA;AAAA,EAIzB,wdAAA;AAAA,EACA;AAAA,IACE,QAAA,EAAU;AAAA,MACR,IAAA,EAAM;AAAA,QACJ,EAAA,EAAI,mBAAA;AAAA,QACJ,EAAA,EAAI,mBAAA;AAAA,QACJ,EAAA,EAAI,mBAAA;AAAA;AAAA,QACsB,KAAA,EAAO,mBAAA;AAAA;AAAA,QACP,MAAA,EAAQ,mBAAA;AAAA;AAAA,QACR,KAAA,EAAO;AAAA;AACnC,KACF;AAAA,IACA,eAAA,EAAiB;AAAA,MACf,IAAA,EAAM;AAAA;AACR;AAEJ,CAAA;AAKA,IAAM,mBAAA,GAAsB,GAAA;AAAA,EAC1B,qIAAA;AAAA,EACA;AAAA,IACE,QAAA,EAAU;AAAA,MACR,IAAA,EAAM;AAAA,QACJ,EAAA,EAAI,QAAA;AAAA,QACJ,EAAA,EAAI,QAAA;AAAA,QACJ,EAAA,EAAI,aAAA;AAAA;AAAA,QACsB,KAAA,EAAO,QAAA;AAAA;AAAA,QACP,MAAA,EAAQ,QAAA;AAAA;AAAA,QACR,KAAA,EAAO;AAAA;AACnC,KACF;AAAA,IACA,eAAA,EAAiB;AAAA,MACf,IAAA,EAAM;AAAA;AACR;AAEJ,CAAA;AAKA,IAAM,MAAA,GAAe,KAAA,CAAA,UAAA;AAAA,EACnB,CAAC,EAAE,SAAA,EAAW,MAAM,GAAG,KAAA,IAAS,GAAA,qBAC9B,GAAA;AAAA,IAAiB,eAAA,CAAA,IAAA;AAAA,IAAhB;AAAA,MACC,WAAA,EAAU,QAAA;AAAA,MACV,WAAW,EAAA,CAAG,kBAAA,CAAmB,EAAE,IAAA,EAAM,GAAG,SAAS,CAAA;AAAA,MACpD,GAAG,KAAA;AAAA,MACJ,GAAA;AAAA,MAEA,QAAA,kBAAA,GAAA,CAAiB,eAAA,CAAA,KAAA,EAAhB,EAAsB,WAAA,EAAU,cAAA,EAAe,SAAA,EAAW,EAAA,CAAG,mBAAA,CAAoB,EAAE,IAAA,EAAM,CAAC,CAAA,EAAG;AAAA;AAAA;AAGpG;AACA,MAAA,CAAO,cAA8B,eAAA,CAAA,IAAA,CAAK,WAAA","file":"switch.js","sourcesContent":["import { type ClassValue, clsx } from 'clsx'\nimport { twMerge } from 'tailwind-merge'\nimport { formatJalaliDate } from '@/lib/jalali-utils'\n\nexport function cn(...inputs: ClassValue[]) {\n return twMerge(clsx(inputs))\n}\n\nexport type SupportedLocale = 'fa' | 'ar' | 'en'\n\n/**\n * Convert digits in a string to Persian/Arabic numerals based on locale.\n * @example convertToLocalNumbers('123', 'fa') => '۱۲۳'\n * @example convertToLocalNumbers('123', 'en') => '123'\n */\nexport function convertToLocalNumbers(text: string | number, locale: SupportedLocale): string {\n if (locale === 'fa' || locale === 'ar') {\n const persianDigits = ['۰', '۱', '۲', '۳', '۴', '۵', '۶', '۷', '۸', '۹']\n return String(text).replace(/\\d/g, (digit) => persianDigits[parseInt(digit)])\n }\n return String(text)\n}\n\n/**\n * Format large numbers with locale-aware suffixes (K/M/B).\n * @example formatLargeNumber(1500, 'fa') => '۱.۵ هزار'\n * @example formatLargeNumber(1500, 'en') => '1.5K'\n */\nexport function formatLargeNumber(num: number, locale: SupportedLocale): string {\n if (num >= 1_000_000_000) {\n const formatted = (num / 1_000_000_000).toFixed(1).replace(/\\.0$/, '')\n return convertToLocalNumbers(formatted, locale) + (locale === 'en' ? 'B' : ' میلیارد')\n }\n if (num >= 1_000_000) {\n const formatted = (num / 1_000_000).toFixed(1).replace(/\\.0$/, '')\n return convertToLocalNumbers(formatted, locale) + (locale === 'en' ? 'M' : ' میلیون')\n }\n if (num >= 1_000) {\n const formatted = (num / 1_000).toFixed(1).replace(/\\.0$/, '')\n return convertToLocalNumbers(formatted, locale) + (locale === 'en' ? 'K' : ' هزار')\n }\n return convertToLocalNumbers(num.toString().replace(/\\B(?=(\\d{3})+(?!\\d))/g, ','), locale)\n}\n\n/**\n * Format number to Instagram-style short format (English only).\n * @example formatNumber(123456, 'short') => '123K'\n * @example formatNumber(123456, 'exact') => '123,456'\n */\nexport function formatNumber(num: number | undefined, format: 'exact' | 'short' = 'exact'): string {\n if (num === undefined || num === null) return '0'\n\n if (format === 'exact') {\n return num.toLocaleString('en-US')\n }\n\n // Short format (Instagram style)\n if (num >= 1_000_000_000) {\n return `${(num / 1_000_000_000).toFixed(1).replace(/\\.0$/, '')}B`\n }\n if (num >= 1_000_000) {\n return `${(num / 1_000_000).toFixed(1).replace(/\\.0$/, '')}M`\n }\n if (num >= 1_000) {\n return `${(num / 1_000).toFixed(1).replace(/\\.0$/, '')}K`\n }\n return num.toString()\n}\n\n/**\n * Format date to relative time with absolute on hover (Persian)\n * @example formatRelativeTime(new Date()) => '۲ ساعت پیش'\n */\nexport function formatRelativeTime(date: Date | string | number): string {\n const now = new Date()\n const then = new Date(date)\n const diffInSeconds = Math.floor((now.getTime() - then.getTime()) / 1000)\n\n if (diffInSeconds < 60) {\n return 'همین الان'\n }\n\n const diffInMinutes = Math.floor(diffInSeconds / 60)\n if (diffInMinutes < 60) {\n return `${convertToLocalNumbers(diffInMinutes, 'fa')} دقیقه پیش`\n }\n\n const diffInHours = Math.floor(diffInMinutes / 60)\n if (diffInHours < 24) {\n return `${convertToLocalNumbers(diffInHours, 'fa')} ساعت پیش`\n }\n\n const diffInDays = Math.floor(diffInHours / 24)\n if (diffInDays < 7) {\n return `${convertToLocalNumbers(diffInDays, 'fa')} روز پیش`\n }\n\n const diffInWeeks = Math.floor(diffInDays / 7)\n if (diffInWeeks < 4) {\n return `${convertToLocalNumbers(diffInWeeks, 'fa')} هفته پیش`\n }\n\n const diffInMonths = Math.floor(diffInDays / 30)\n if (diffInMonths < 12) {\n return `${convertToLocalNumbers(diffInMonths, 'fa')} ماه پیش`\n }\n\n const diffInYears = Math.floor(diffInDays / 365)\n return `${convertToLocalNumbers(diffInYears, 'fa')} سال پیش`\n}\n\n/**\n * Format date to absolute format (Persian / Jalali)\n * Uses date-fns-jalali for accurate Jalali conversion.\n * @example formatAbsoluteTime(new Date()) => '۱۵ دی ۱۴۰۳، ۱۵:۳۰'\n */\nexport function formatAbsoluteTime(date: Date | string | number): string {\n const d = new Date(date)\n return formatJalaliDate(d, 'd MMMM yyyy، HH:mm')\n}\n","'use client'\n\nimport * as React from 'react'\nimport * as SwitchPrimitive from '@radix-ui/react-switch'\nimport { VariantProps, cva } from 'class-variance-authority'\n\nimport { cn } from '@/lib/utils'\n\nconst switchRootVariants = cva(\n // Hover-checked uses brand-600 in light (darker green) and brand-500 in dark (also darker\n // since --brand-* scale inverts across themes). Without the dark variant the track would get\n // *lighter* on hover in dark mode and the white thumb would lose contrast.\n 'peer inline-flex shrink-0 cursor-pointer items-center rounded-full border transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-brand data-[state=checked]:hover:bg-brand-600/90 dark:data-[state=checked]:hover:bg-brand-500/90 data-[state=unchecked]:bg-control data-[state=unchecked]:hover:bg-border',\n {\n variants: {\n size: {\n sm: 'h-[16px] w-[28px]',\n md: 'h-[20px] w-[34px]',\n lg: 'h-[24px] w-[44px]',\n /** @deprecated Use sm */ small: 'h-[16px] w-[28px]',\n /** @deprecated Use md */ medium: 'h-[20px] w-[34px]',\n /** @deprecated Use lg */ large: 'h-[24px] w-[44px]',\n },\n },\n defaultVariants: {\n size: 'sm',\n },\n }\n)\n\n// Checked thumb uses --on-brand (white in both themes) so the thumb stays bright on the\n// brand-colored track regardless of theme. Unchecked thumb uses foreground-lighter for a\n// subdued gray on the muted track.\nconst switchThumbVariants = cva(\n 'pointer-events-none block rounded-full bg-foreground-lighter data-[state=checked]:bg-on-brand shadow-lg ring-0 transition-transform',\n {\n variants: {\n size: {\n sm: 'size-3',\n md: 'size-4',\n lg: 'size-[18px]',\n /** @deprecated Use sm */ small: 'size-3',\n /** @deprecated Use md */ medium: 'size-4',\n /** @deprecated Use lg */ large: 'size-[18px]',\n },\n },\n defaultVariants: {\n size: 'sm',\n },\n }\n)\n\nexport interface SwitchProps\n extends React.ComponentPropsWithoutRef<typeof SwitchPrimitive.Root>, VariantProps<typeof switchRootVariants> {}\n\nconst Switch = React.forwardRef<React.ElementRef<typeof SwitchPrimitive.Root>, SwitchProps>(\n ({ className, size, ...props }, ref) => (\n <SwitchPrimitive.Root\n data-slot=\"switch\"\n className={cn(switchRootVariants({ size }), className)}\n {...props}\n ref={ref}\n >\n <SwitchPrimitive.Thumb data-slot=\"switch-thumb\" className={cn(switchThumbVariants({ size }))} />\n </SwitchPrimitive.Root>\n )\n)\nSwitch.displayName = SwitchPrimitive.Root.displayName\n\nexport { Switch }\n"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/lib/utils.ts","../../../src/components/ui/table.tsx"],"names":["twMerge","clsx","jsx","jsxs"],"mappings":"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","file":"table.cjs","sourcesContent":["import { type ClassValue, clsx } from 'clsx'\nimport { twMerge } from 'tailwind-merge'\nimport { formatJalaliDate } from '@/lib/jalali-utils'\n\nexport function cn(...inputs: ClassValue[]) {\n return twMerge(clsx(inputs))\n}\n\nexport type SupportedLocale = 'fa' | 'ar' | 'en'\n\n/**\n * Convert digits in a string to Persian/Arabic numerals based on locale.\n * @example convertToLocalNumbers('123', 'fa') => '۱۲۳'\n * @example convertToLocalNumbers('123', 'en') => '123'\n */\nexport function convertToLocalNumbers(text: string | number, locale: SupportedLocale): string {\n if (locale === 'fa' || locale === 'ar') {\n const persianDigits = ['۰', '۱', '۲', '۳', '۴', '۵', '۶', '۷', '۸', '۹']\n return String(text).replace(/\\d/g, (digit) => persianDigits[parseInt(digit)])\n }\n return String(text)\n}\n\n/**\n * Format large numbers with locale-aware suffixes (K/M/B).\n * @example formatLargeNumber(1500, 'fa') => '۱.۵ هزار'\n * @example formatLargeNumber(1500, 'en') => '1.5K'\n */\nexport function formatLargeNumber(num: number, locale: SupportedLocale): string {\n if (num >= 1_000_000_000) {\n const formatted = (num / 1_000_000_000).toFixed(1).replace(/\\.0$/, '')\n return convertToLocalNumbers(formatted, locale) + (locale === 'en' ? 'B' : ' میلیارد')\n }\n if (num >= 1_000_000) {\n const formatted = (num / 1_000_000).toFixed(1).replace(/\\.0$/, '')\n return convertToLocalNumbers(formatted, locale) + (locale === 'en' ? 'M' : ' میلیون')\n }\n if (num >= 1_000) {\n const formatted = (num / 1_000).toFixed(1).replace(/\\.0$/, '')\n return convertToLocalNumbers(formatted, locale) + (locale === 'en' ? 'K' : ' هزار')\n }\n return convertToLocalNumbers(num.toString().replace(/\\B(?=(\\d{3})+(?!\\d))/g, ','), locale)\n}\n\n/**\n * Format number to Instagram-style short format (English only).\n * @example formatNumber(123456, 'short') => '123K'\n * @example formatNumber(123456, 'exact') => '123,456'\n */\nexport function formatNumber(num: number | undefined, format: 'exact' | 'short' = 'exact'): string {\n if (num === undefined || num === null) return '0'\n\n if (format === 'exact') {\n return num.toLocaleString('en-US')\n }\n\n // Short format (Instagram style)\n if (num >= 1_000_000_000) {\n return `${(num / 1_000_000_000).toFixed(1).replace(/\\.0$/, '')}B`\n }\n if (num >= 1_000_000) {\n return `${(num / 1_000_000).toFixed(1).replace(/\\.0$/, '')}M`\n }\n if (num >= 1_000) {\n return `${(num / 1_000).toFixed(1).replace(/\\.0$/, '')}K`\n }\n return num.toString()\n}\n\n/**\n * Format date to relative time with absolute on hover (Persian)\n * @example formatRelativeTime(new Date()) => '۲ ساعت پیش'\n */\nexport function formatRelativeTime(date: Date | string | number): string {\n const now = new Date()\n const then = new Date(date)\n const diffInSeconds = Math.floor((now.getTime() - then.getTime()) / 1000)\n\n if (diffInSeconds < 60) {\n return 'همین الان'\n }\n\n const diffInMinutes = Math.floor(diffInSeconds / 60)\n if (diffInMinutes < 60) {\n return `${convertToLocalNumbers(diffInMinutes, 'fa')} دقیقه پیش`\n }\n\n const diffInHours = Math.floor(diffInMinutes / 60)\n if (diffInHours < 24) {\n return `${convertToLocalNumbers(diffInHours, 'fa')} ساعت پیش`\n }\n\n const diffInDays = Math.floor(diffInHours / 24)\n if (diffInDays < 7) {\n return `${convertToLocalNumbers(diffInDays, 'fa')} روز پیش`\n }\n\n const diffInWeeks = Math.floor(diffInDays / 7)\n if (diffInWeeks < 4) {\n return `${convertToLocalNumbers(diffInWeeks, 'fa')} هفته پیش`\n }\n\n const diffInMonths = Math.floor(diffInDays / 30)\n if (diffInMonths < 12) {\n return `${convertToLocalNumbers(diffInMonths, 'fa')} ماه پیش`\n }\n\n const diffInYears = Math.floor(diffInDays / 365)\n return `${convertToLocalNumbers(diffInYears, 'fa')} سال پیش`\n}\n\n/**\n * Format date to absolute format (Persian / Jalali)\n * Uses date-fns-jalali for accurate Jalali conversion.\n * @example formatAbsoluteTime(new Date()) => '۱۵ دی ۱۴۰۳، ۱۵:۳۰'\n */\nexport function formatAbsoluteTime(date: Date | string | number): string {\n const d = new Date(date)\n return formatJalaliDate(d, 'd MMMM yyyy، HH:mm')\n}\n","'use client'\n\nimport * as React from 'react'\n\nimport { cn } from '@/lib/utils'\n\n/* -------------------------------------------------------------------------- */\n/* Table */\n/* -------------------------------------------------------------------------- */\n\ninterface TableProps extends React.ComponentProps<'table'> {\n /** Size variant affecting cell padding and font size */\n size?: 'sm' | 'md' | 'lg'\n /** Zebra-striped rows */\n striped?: boolean\n /** Bordered cells */\n bordered?: boolean\n /** Sticky header that stays visible when scrolling */\n stickyHeader?: boolean\n}\n\nfunction Table({\n className,\n size = 'md',\n striped = false,\n bordered = false,\n stickyHeader = false,\n ...props\n}: TableProps) {\n return (\n <div data-slot=\"table-container\" className=\"relative w-full overflow-x-auto\">\n <table\n data-slot=\"table\"\n data-size={size}\n data-striped={striped || undefined}\n data-bordered={bordered || undefined}\n data-sticky-header={stickyHeader || undefined}\n className={cn('w-full caption-bottom', className)}\n {...props}\n />\n </div>\n )\n}\n\n/* -------------------------------------------------------------------------- */\n/* TableHeader */\n/* -------------------------------------------------------------------------- */\n\nfunction TableHeader({ className, ...props }: React.ComponentProps<'thead'>) {\n return (\n <thead\n data-slot=\"table-header\"\n className={cn(\n // 1.1.16 — internal row dividers use --border-muted (subtle), same\n // rationale as Card. The page-level rule of thumb: borders that\n // separate sections of one surface should whisper, not shout.\n '[&_tr]:border-b [&_tr]:border-muted bg-surface-75',\n '[[data-sticky-header]_&]:sticky [[data-sticky-header]_&]:top-0 [[data-sticky-header]_&]:z-[9] [[data-sticky-header]_&]:bg-surface-75',\n className\n )}\n {...props}\n />\n )\n}\n\n/* -------------------------------------------------------------------------- */\n/* TableBody */\n/* -------------------------------------------------------------------------- */\n\nfunction TableBody({ className, ...props }: React.ComponentProps<'tbody'>) {\n return (\n <tbody\n data-slot=\"table-body\"\n className={cn(\n '[&_tr:last-child]:border-0',\n '[[data-striped]_&_tr:nth-child(even)]:bg-surface-75 [[data-striped]_&_tr:nth-child(even):hover]:bg-surface-200',\n className\n )}\n {...props}\n />\n )\n}\n\n/* -------------------------------------------------------------------------- */\n/* TableFooter */\n/* -------------------------------------------------------------------------- */\n\nfunction TableFooter({ className, ...props }: React.ComponentProps<'tfoot'>) {\n return (\n <tfoot\n data-slot=\"table-footer\"\n className={cn('bg-muted/50 border-t font-medium [&>tr]:last:border-b-0', className)}\n {...props}\n />\n )\n}\n\n/* -------------------------------------------------------------------------- */\n/* TableRow */\n/* -------------------------------------------------------------------------- */\n\nfunction TableRow({ className, ...props }: React.ComponentProps<'tr'>) {\n return (\n <tr\n data-slot=\"table-row\"\n // 1.1.16 — see TableHeader: row dividers use --border-muted so the\n // table reads as a continuous list, not a fence of bright lines.\n className={cn(\n 'hover:bg-surface-200 data-[state=selected]:bg-selection border-b border-muted transition-colors',\n className\n )}\n {...props}\n />\n )\n}\n\n/* -------------------------------------------------------------------------- */\n/* TableHead */\n/* -------------------------------------------------------------------------- */\n\ninterface TableHeadProps extends React.ComponentProps<'th'> {\n /** Sort direction — sets aria-sort on the <th> element */\n sortDirection?: 'asc' | 'desc' | 'none'\n}\n\nfunction TableHead({ className, sortDirection, ...props }: TableHeadProps) {\n const ariaSort =\n sortDirection === 'asc'\n ? 'ascending'\n : sortDirection === 'desc'\n ? 'descending'\n : sortDirection === 'none'\n ? 'none'\n : undefined\n\n return (\n <th\n data-slot=\"table-head\"\n aria-sort={ariaSort}\n className={cn(\n 'text-foreground-lighter text-start align-middle font-semibold whitespace-nowrap [&:has([role=checkbox])]:pe-0 [&>[role=checkbox]]:translate-y-[2px]',\n // Size variants (inherited from parent table)\n '[[data-size=sm]_&]:h-8 [[data-size=sm]_&]:px-2 [[data-size=sm]_&]:text-xs',\n '[[data-size=md]_&]:h-10 [[data-size=md]_&]:px-3 [[data-size=md]_&]:text-sm',\n '[[data-size=lg]_&]:h-12 [[data-size=lg]_&]:px-4 [[data-size=lg]_&]:text-base',\n // Bordered variant\n '[[data-bordered]_&]:border [[data-bordered]_&]:border-border',\n className\n )}\n {...props}\n />\n )\n}\n\n/* -------------------------------------------------------------------------- */\n/* TableCell */\n/* -------------------------------------------------------------------------- */\n\nfunction TableCell({ className, ...props }: React.ComponentProps<'td'>) {\n return (\n <td\n data-slot=\"table-cell\"\n className={cn(\n 'align-middle whitespace-nowrap [&:has([role=checkbox])]:pe-0 [&>[role=checkbox]]:translate-y-[2px]',\n // Size variants (inherited from parent table)\n '[[data-size=sm]_&]:px-2 [[data-size=sm]_&]:py-1.5 [[data-size=sm]_&]:text-xs',\n '[[data-size=md]_&]:px-3 [[data-size=md]_&]:py-2 [[data-size=md]_&]:text-sm',\n '[[data-size=lg]_&]:px-4 [[data-size=lg]_&]:py-3 [[data-size=lg]_&]:text-base',\n // Bordered variant\n '[[data-bordered]_&]:border [[data-bordered]_&]:border-border',\n className\n )}\n {...props}\n />\n )\n}\n\n/* -------------------------------------------------------------------------- */\n/* TableCaption */\n/* -------------------------------------------------------------------------- */\n\nfunction TableCaption({ className, ...props }: React.ComponentProps<'caption'>) {\n return (\n <caption data-slot=\"table-caption\" className={cn('text-foreground-muted mt-4 text-sm', className)} {...props} />\n )\n}\n\n/* -------------------------------------------------------------------------- */\n/* TableSortHeader — clickable sort indicator for table headers */\n/* -------------------------------------------------------------------------- */\n\ninterface TableSortHeaderProps extends React.ComponentProps<'button'> {\n /** Current sort direction */\n sorted?: 'asc' | 'desc' | false\n}\n\nfunction TableSortHeader({ className, children, sorted, ...props }: TableSortHeaderProps) {\n return (\n <button\n data-slot=\"table-sort-header\"\n className={cn(\n 'inline-flex items-center gap-1.5 cursor-pointer select-none hover:text-foreground transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1 rounded-sm',\n sorted && 'text-foreground',\n className\n )}\n {...props}\n >\n {children}\n <span className=\"inline-flex flex-col -space-y-0.5\">\n <svg\n width=\"8\"\n height=\"5\"\n viewBox=\"0 0 8 5\"\n className={cn('transition-colors', sorted === 'asc' ? 'text-foreground' : 'text-foreground-muted')}\n >\n <path d=\"M4 0L8 5H0L4 0Z\" fill=\"currentColor\" />\n </svg>\n <svg\n width=\"8\"\n height=\"5\"\n viewBox=\"0 0 8 5\"\n className={cn('transition-colors', sorted === 'desc' ? 'text-foreground' : 'text-foreground-muted')}\n >\n <path d=\"M4 5L0 0H8L4 5Z\" fill=\"currentColor\" />\n </svg>\n </span>\n </button>\n )\n}\n\nTable.displayName = 'Table'\nTableHeader.displayName = 'TableHeader'\nTableBody.displayName = 'TableBody'\nTableFooter.displayName = 'TableFooter'\nTableHead.displayName = 'TableHead'\nTableRow.displayName = 'TableRow'\nTableCell.displayName = 'TableCell'\nTableCaption.displayName = 'TableCaption'\nTableSortHeader.displayName = 'TableSortHeader'\n\nexport { Table, TableHeader, TableBody, TableFooter, TableHead, TableRow, TableCell, TableCaption, TableSortHeader }\n"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/lib/utils.ts","../../../src/components/ui/table.tsx"],"names":[],"mappings":"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","file":"table.js","sourcesContent":["import { type ClassValue, clsx } from 'clsx'\nimport { twMerge } from 'tailwind-merge'\nimport { formatJalaliDate } from '@/lib/jalali-utils'\n\nexport function cn(...inputs: ClassValue[]) {\n return twMerge(clsx(inputs))\n}\n\nexport type SupportedLocale = 'fa' | 'ar' | 'en'\n\n/**\n * Convert digits in a string to Persian/Arabic numerals based on locale.\n * @example convertToLocalNumbers('123', 'fa') => '۱۲۳'\n * @example convertToLocalNumbers('123', 'en') => '123'\n */\nexport function convertToLocalNumbers(text: string | number, locale: SupportedLocale): string {\n if (locale === 'fa' || locale === 'ar') {\n const persianDigits = ['۰', '۱', '۲', '۳', '۴', '۵', '۶', '۷', '۸', '۹']\n return String(text).replace(/\\d/g, (digit) => persianDigits[parseInt(digit)])\n }\n return String(text)\n}\n\n/**\n * Format large numbers with locale-aware suffixes (K/M/B).\n * @example formatLargeNumber(1500, 'fa') => '۱.۵ هزار'\n * @example formatLargeNumber(1500, 'en') => '1.5K'\n */\nexport function formatLargeNumber(num: number, locale: SupportedLocale): string {\n if (num >= 1_000_000_000) {\n const formatted = (num / 1_000_000_000).toFixed(1).replace(/\\.0$/, '')\n return convertToLocalNumbers(formatted, locale) + (locale === 'en' ? 'B' : ' میلیارد')\n }\n if (num >= 1_000_000) {\n const formatted = (num / 1_000_000).toFixed(1).replace(/\\.0$/, '')\n return convertToLocalNumbers(formatted, locale) + (locale === 'en' ? 'M' : ' میلیون')\n }\n if (num >= 1_000) {\n const formatted = (num / 1_000).toFixed(1).replace(/\\.0$/, '')\n return convertToLocalNumbers(formatted, locale) + (locale === 'en' ? 'K' : ' هزار')\n }\n return convertToLocalNumbers(num.toString().replace(/\\B(?=(\\d{3})+(?!\\d))/g, ','), locale)\n}\n\n/**\n * Format number to Instagram-style short format (English only).\n * @example formatNumber(123456, 'short') => '123K'\n * @example formatNumber(123456, 'exact') => '123,456'\n */\nexport function formatNumber(num: number | undefined, format: 'exact' | 'short' = 'exact'): string {\n if (num === undefined || num === null) return '0'\n\n if (format === 'exact') {\n return num.toLocaleString('en-US')\n }\n\n // Short format (Instagram style)\n if (num >= 1_000_000_000) {\n return `${(num / 1_000_000_000).toFixed(1).replace(/\\.0$/, '')}B`\n }\n if (num >= 1_000_000) {\n return `${(num / 1_000_000).toFixed(1).replace(/\\.0$/, '')}M`\n }\n if (num >= 1_000) {\n return `${(num / 1_000).toFixed(1).replace(/\\.0$/, '')}K`\n }\n return num.toString()\n}\n\n/**\n * Format date to relative time with absolute on hover (Persian)\n * @example formatRelativeTime(new Date()) => '۲ ساعت پیش'\n */\nexport function formatRelativeTime(date: Date | string | number): string {\n const now = new Date()\n const then = new Date(date)\n const diffInSeconds = Math.floor((now.getTime() - then.getTime()) / 1000)\n\n if (diffInSeconds < 60) {\n return 'همین الان'\n }\n\n const diffInMinutes = Math.floor(diffInSeconds / 60)\n if (diffInMinutes < 60) {\n return `${convertToLocalNumbers(diffInMinutes, 'fa')} دقیقه پیش`\n }\n\n const diffInHours = Math.floor(diffInMinutes / 60)\n if (diffInHours < 24) {\n return `${convertToLocalNumbers(diffInHours, 'fa')} ساعت پیش`\n }\n\n const diffInDays = Math.floor(diffInHours / 24)\n if (diffInDays < 7) {\n return `${convertToLocalNumbers(diffInDays, 'fa')} روز پیش`\n }\n\n const diffInWeeks = Math.floor(diffInDays / 7)\n if (diffInWeeks < 4) {\n return `${convertToLocalNumbers(diffInWeeks, 'fa')} هفته پیش`\n }\n\n const diffInMonths = Math.floor(diffInDays / 30)\n if (diffInMonths < 12) {\n return `${convertToLocalNumbers(diffInMonths, 'fa')} ماه پیش`\n }\n\n const diffInYears = Math.floor(diffInDays / 365)\n return `${convertToLocalNumbers(diffInYears, 'fa')} سال پیش`\n}\n\n/**\n * Format date to absolute format (Persian / Jalali)\n * Uses date-fns-jalali for accurate Jalali conversion.\n * @example formatAbsoluteTime(new Date()) => '۱۵ دی ۱۴۰۳، ۱۵:۳۰'\n */\nexport function formatAbsoluteTime(date: Date | string | number): string {\n const d = new Date(date)\n return formatJalaliDate(d, 'd MMMM yyyy، HH:mm')\n}\n","'use client'\n\nimport * as React from 'react'\n\nimport { cn } from '@/lib/utils'\n\n/* -------------------------------------------------------------------------- */\n/* Table */\n/* -------------------------------------------------------------------------- */\n\ninterface TableProps extends React.ComponentProps<'table'> {\n /** Size variant affecting cell padding and font size */\n size?: 'sm' | 'md' | 'lg'\n /** Zebra-striped rows */\n striped?: boolean\n /** Bordered cells */\n bordered?: boolean\n /** Sticky header that stays visible when scrolling */\n stickyHeader?: boolean\n}\n\nfunction Table({\n className,\n size = 'md',\n striped = false,\n bordered = false,\n stickyHeader = false,\n ...props\n}: TableProps) {\n return (\n <div data-slot=\"table-container\" className=\"relative w-full overflow-x-auto\">\n <table\n data-slot=\"table\"\n data-size={size}\n data-striped={striped || undefined}\n data-bordered={bordered || undefined}\n data-sticky-header={stickyHeader || undefined}\n className={cn('w-full caption-bottom', className)}\n {...props}\n />\n </div>\n )\n}\n\n/* -------------------------------------------------------------------------- */\n/* TableHeader */\n/* -------------------------------------------------------------------------- */\n\nfunction TableHeader({ className, ...props }: React.ComponentProps<'thead'>) {\n return (\n <thead\n data-slot=\"table-header\"\n className={cn(\n // 1.1.16 — internal row dividers use --border-muted (subtle), same\n // rationale as Card. The page-level rule of thumb: borders that\n // separate sections of one surface should whisper, not shout.\n '[&_tr]:border-b [&_tr]:border-muted bg-surface-75',\n '[[data-sticky-header]_&]:sticky [[data-sticky-header]_&]:top-0 [[data-sticky-header]_&]:z-[9] [[data-sticky-header]_&]:bg-surface-75',\n className\n )}\n {...props}\n />\n )\n}\n\n/* -------------------------------------------------------------------------- */\n/* TableBody */\n/* -------------------------------------------------------------------------- */\n\nfunction TableBody({ className, ...props }: React.ComponentProps<'tbody'>) {\n return (\n <tbody\n data-slot=\"table-body\"\n className={cn(\n '[&_tr:last-child]:border-0',\n '[[data-striped]_&_tr:nth-child(even)]:bg-surface-75 [[data-striped]_&_tr:nth-child(even):hover]:bg-surface-200',\n className\n )}\n {...props}\n />\n )\n}\n\n/* -------------------------------------------------------------------------- */\n/* TableFooter */\n/* -------------------------------------------------------------------------- */\n\nfunction TableFooter({ className, ...props }: React.ComponentProps<'tfoot'>) {\n return (\n <tfoot\n data-slot=\"table-footer\"\n className={cn('bg-muted/50 border-t font-medium [&>tr]:last:border-b-0', className)}\n {...props}\n />\n )\n}\n\n/* -------------------------------------------------------------------------- */\n/* TableRow */\n/* -------------------------------------------------------------------------- */\n\nfunction TableRow({ className, ...props }: React.ComponentProps<'tr'>) {\n return (\n <tr\n data-slot=\"table-row\"\n // 1.1.16 — see TableHeader: row dividers use --border-muted so the\n // table reads as a continuous list, not a fence of bright lines.\n className={cn(\n 'hover:bg-surface-200 data-[state=selected]:bg-selection border-b border-muted transition-colors',\n className\n )}\n {...props}\n />\n )\n}\n\n/* -------------------------------------------------------------------------- */\n/* TableHead */\n/* -------------------------------------------------------------------------- */\n\ninterface TableHeadProps extends React.ComponentProps<'th'> {\n /** Sort direction — sets aria-sort on the <th> element */\n sortDirection?: 'asc' | 'desc' | 'none'\n}\n\nfunction TableHead({ className, sortDirection, ...props }: TableHeadProps) {\n const ariaSort =\n sortDirection === 'asc'\n ? 'ascending'\n : sortDirection === 'desc'\n ? 'descending'\n : sortDirection === 'none'\n ? 'none'\n : undefined\n\n return (\n <th\n data-slot=\"table-head\"\n aria-sort={ariaSort}\n className={cn(\n 'text-foreground-lighter text-start align-middle font-semibold whitespace-nowrap [&:has([role=checkbox])]:pe-0 [&>[role=checkbox]]:translate-y-[2px]',\n // Size variants (inherited from parent table)\n '[[data-size=sm]_&]:h-8 [[data-size=sm]_&]:px-2 [[data-size=sm]_&]:text-xs',\n '[[data-size=md]_&]:h-10 [[data-size=md]_&]:px-3 [[data-size=md]_&]:text-sm',\n '[[data-size=lg]_&]:h-12 [[data-size=lg]_&]:px-4 [[data-size=lg]_&]:text-base',\n // Bordered variant\n '[[data-bordered]_&]:border [[data-bordered]_&]:border-border',\n className\n )}\n {...props}\n />\n )\n}\n\n/* -------------------------------------------------------------------------- */\n/* TableCell */\n/* -------------------------------------------------------------------------- */\n\nfunction TableCell({ className, ...props }: React.ComponentProps<'td'>) {\n return (\n <td\n data-slot=\"table-cell\"\n className={cn(\n 'align-middle whitespace-nowrap [&:has([role=checkbox])]:pe-0 [&>[role=checkbox]]:translate-y-[2px]',\n // Size variants (inherited from parent table)\n '[[data-size=sm]_&]:px-2 [[data-size=sm]_&]:py-1.5 [[data-size=sm]_&]:text-xs',\n '[[data-size=md]_&]:px-3 [[data-size=md]_&]:py-2 [[data-size=md]_&]:text-sm',\n '[[data-size=lg]_&]:px-4 [[data-size=lg]_&]:py-3 [[data-size=lg]_&]:text-base',\n // Bordered variant\n '[[data-bordered]_&]:border [[data-bordered]_&]:border-border',\n className\n )}\n {...props}\n />\n )\n}\n\n/* -------------------------------------------------------------------------- */\n/* TableCaption */\n/* -------------------------------------------------------------------------- */\n\nfunction TableCaption({ className, ...props }: React.ComponentProps<'caption'>) {\n return (\n <caption data-slot=\"table-caption\" className={cn('text-foreground-muted mt-4 text-sm', className)} {...props} />\n )\n}\n\n/* -------------------------------------------------------------------------- */\n/* TableSortHeader — clickable sort indicator for table headers */\n/* -------------------------------------------------------------------------- */\n\ninterface TableSortHeaderProps extends React.ComponentProps<'button'> {\n /** Current sort direction */\n sorted?: 'asc' | 'desc' | false\n}\n\nfunction TableSortHeader({ className, children, sorted, ...props }: TableSortHeaderProps) {\n return (\n <button\n data-slot=\"table-sort-header\"\n className={cn(\n 'inline-flex items-center gap-1.5 cursor-pointer select-none hover:text-foreground transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1 rounded-sm',\n sorted && 'text-foreground',\n className\n )}\n {...props}\n >\n {children}\n <span className=\"inline-flex flex-col -space-y-0.5\">\n <svg\n width=\"8\"\n height=\"5\"\n viewBox=\"0 0 8 5\"\n className={cn('transition-colors', sorted === 'asc' ? 'text-foreground' : 'text-foreground-muted')}\n >\n <path d=\"M4 0L8 5H0L4 0Z\" fill=\"currentColor\" />\n </svg>\n <svg\n width=\"8\"\n height=\"5\"\n viewBox=\"0 0 8 5\"\n className={cn('transition-colors', sorted === 'desc' ? 'text-foreground' : 'text-foreground-muted')}\n >\n <path d=\"M4 5L0 0H8L4 5Z\" fill=\"currentColor\" />\n </svg>\n </span>\n </button>\n )\n}\n\nTable.displayName = 'Table'\nTableHeader.displayName = 'TableHeader'\nTableBody.displayName = 'TableBody'\nTableFooter.displayName = 'TableFooter'\nTableHead.displayName = 'TableHead'\nTableRow.displayName = 'TableRow'\nTableCell.displayName = 'TableCell'\nTableCaption.displayName = 'TableCaption'\nTableSortHeader.displayName = 'TableSortHeader'\n\nexport { Table, TableHeader, TableBody, TableFooter, TableHead, TableRow, TableCell, TableCaption, TableSortHeader }\n"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/hooks/use-document-direction.ts","../../../src/lib/utils.ts","../../../src/components/ui/tabs.tsx"],"names":["useState","useEffect","twMerge","clsx","React","TabsPrimitive","jsx","jsxs"],"mappings":"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","file":"tabs.cjs","sourcesContent":["'use client'\n\nimport { useState, useEffect } from 'react'\n\ntype Direction = 'ltr' | 'rtl'\n\nfunction useDocumentDirection(): Direction {\n const [direction, setDirection] = useState<Direction>('rtl')\n\n useEffect(() => {\n const getDirection = (): Direction => {\n if (typeof document === 'undefined') return 'rtl'\n const htmlDir = document.documentElement.getAttribute('dir')\n if (htmlDir === 'ltr' || htmlDir === 'rtl') {\n return htmlDir\n }\n const computedDir = window.getComputedStyle(document.documentElement).direction\n return computedDir === 'rtl' ? 'rtl' : 'ltr'\n }\n\n setDirection(getDirection())\n\n const observer = new MutationObserver(() => {\n setDirection(getDirection())\n })\n\n observer.observe(document.documentElement, {\n attributes: true,\n attributeFilter: ['dir'],\n })\n\n return () => observer.disconnect()\n }, [])\n\n return direction\n}\n\nexport { useDocumentDirection }\nexport type { Direction }\n","import { type ClassValue, clsx } from 'clsx'\nimport { twMerge } from 'tailwind-merge'\nimport { formatJalaliDate } from '@/lib/jalali-utils'\n\nexport function cn(...inputs: ClassValue[]) {\n return twMerge(clsx(inputs))\n}\n\nexport type SupportedLocale = 'fa' | 'ar' | 'en'\n\n/**\n * Convert digits in a string to Persian/Arabic numerals based on locale.\n * @example convertToLocalNumbers('123', 'fa') => '۱۲۳'\n * @example convertToLocalNumbers('123', 'en') => '123'\n */\nexport function convertToLocalNumbers(text: string | number, locale: SupportedLocale): string {\n if (locale === 'fa' || locale === 'ar') {\n const persianDigits = ['۰', '۱', '۲', '۳', '۴', '۵', '۶', '۷', '۸', '۹']\n return String(text).replace(/\\d/g, (digit) => persianDigits[parseInt(digit)])\n }\n return String(text)\n}\n\n/**\n * Format large numbers with locale-aware suffixes (K/M/B).\n * @example formatLargeNumber(1500, 'fa') => '۱.۵ هزار'\n * @example formatLargeNumber(1500, 'en') => '1.5K'\n */\nexport function formatLargeNumber(num: number, locale: SupportedLocale): string {\n if (num >= 1_000_000_000) {\n const formatted = (num / 1_000_000_000).toFixed(1).replace(/\\.0$/, '')\n return convertToLocalNumbers(formatted, locale) + (locale === 'en' ? 'B' : ' میلیارد')\n }\n if (num >= 1_000_000) {\n const formatted = (num / 1_000_000).toFixed(1).replace(/\\.0$/, '')\n return convertToLocalNumbers(formatted, locale) + (locale === 'en' ? 'M' : ' میلیون')\n }\n if (num >= 1_000) {\n const formatted = (num / 1_000).toFixed(1).replace(/\\.0$/, '')\n return convertToLocalNumbers(formatted, locale) + (locale === 'en' ? 'K' : ' هزار')\n }\n return convertToLocalNumbers(num.toString().replace(/\\B(?=(\\d{3})+(?!\\d))/g, ','), locale)\n}\n\n/**\n * Format number to Instagram-style short format (English only).\n * @example formatNumber(123456, 'short') => '123K'\n * @example formatNumber(123456, 'exact') => '123,456'\n */\nexport function formatNumber(num: number | undefined, format: 'exact' | 'short' = 'exact'): string {\n if (num === undefined || num === null) return '0'\n\n if (format === 'exact') {\n return num.toLocaleString('en-US')\n }\n\n // Short format (Instagram style)\n if (num >= 1_000_000_000) {\n return `${(num / 1_000_000_000).toFixed(1).replace(/\\.0$/, '')}B`\n }\n if (num >= 1_000_000) {\n return `${(num / 1_000_000).toFixed(1).replace(/\\.0$/, '')}M`\n }\n if (num >= 1_000) {\n return `${(num / 1_000).toFixed(1).replace(/\\.0$/, '')}K`\n }\n return num.toString()\n}\n\n/**\n * Format date to relative time with absolute on hover (Persian)\n * @example formatRelativeTime(new Date()) => '۲ ساعت پیش'\n */\nexport function formatRelativeTime(date: Date | string | number): string {\n const now = new Date()\n const then = new Date(date)\n const diffInSeconds = Math.floor((now.getTime() - then.getTime()) / 1000)\n\n if (diffInSeconds < 60) {\n return 'همین الان'\n }\n\n const diffInMinutes = Math.floor(diffInSeconds / 60)\n if (diffInMinutes < 60) {\n return `${convertToLocalNumbers(diffInMinutes, 'fa')} دقیقه پیش`\n }\n\n const diffInHours = Math.floor(diffInMinutes / 60)\n if (diffInHours < 24) {\n return `${convertToLocalNumbers(diffInHours, 'fa')} ساعت پیش`\n }\n\n const diffInDays = Math.floor(diffInHours / 24)\n if (diffInDays < 7) {\n return `${convertToLocalNumbers(diffInDays, 'fa')} روز پیش`\n }\n\n const diffInWeeks = Math.floor(diffInDays / 7)\n if (diffInWeeks < 4) {\n return `${convertToLocalNumbers(diffInWeeks, 'fa')} هفته پیش`\n }\n\n const diffInMonths = Math.floor(diffInDays / 30)\n if (diffInMonths < 12) {\n return `${convertToLocalNumbers(diffInMonths, 'fa')} ماه پیش`\n }\n\n const diffInYears = Math.floor(diffInDays / 365)\n return `${convertToLocalNumbers(diffInYears, 'fa')} سال پیش`\n}\n\n/**\n * Format date to absolute format (Persian / Jalali)\n * Uses date-fns-jalali for accurate Jalali conversion.\n * @example formatAbsoluteTime(new Date()) => '۱۵ دی ۱۴۰۳، ۱۵:۳۰'\n */\nexport function formatAbsoluteTime(date: Date | string | number): string {\n const d = new Date(date)\n return formatJalaliDate(d, 'd MMMM yyyy، HH:mm')\n}\n","'use client'\n\nimport * as React from 'react'\nimport * as TabsPrimitive from '@radix-ui/react-tabs'\n\nimport { useDocumentDirection, type Direction } from '@/hooks/use-document-direction'\nimport { cn } from '@/lib/utils'\n\n/* -------------------------------------------------------------------------- */\n/* Tabs Root */\n/* -------------------------------------------------------------------------- */\n\nconst Tabs = React.forwardRef<\n React.ElementRef<typeof TabsPrimitive.Root>,\n React.ComponentPropsWithoutRef<typeof TabsPrimitive.Root>\n>(({ dir, ...props }, ref) => {\n // Forward document `dir` to Radix so ArrowLeft/ArrowRight semantics flip in RTL.\n const documentDir = useDocumentDirection()\n const resolvedDir = (dir as Direction) ?? documentDir\n return <TabsPrimitive.Root ref={ref} dir={resolvedDir} {...props} />\n})\nTabs.displayName = 'Tabs'\n\n/* -------------------------------------------------------------------------- */\n/* TabsList */\n/* -------------------------------------------------------------------------- */\n\ninterface TabsListProps extends React.ComponentPropsWithoutRef<typeof TabsPrimitive.List> {\n /** Visual variant */\n variant?: 'default' | 'underline' | 'outline'\n /** Size */\n size?: 'sm' | 'md' | 'lg'\n}\n\nconst TabsList = React.forwardRef<React.ElementRef<typeof TabsPrimitive.List>, TabsListProps>(\n ({ className, variant = 'default', size = 'md', ...props }, ref) => (\n <TabsPrimitive.List\n ref={ref}\n data-slot=\"tabs-list\"\n data-variant={variant}\n data-size={size}\n className={cn(\n 'inline-flex items-center text-foreground-lighter',\n // Default (pill) variant\n variant === 'default' && 'rounded-lg bg-surface-200 p-1 gap-1',\n // Underline variant\n variant === 'underline' && 'border-b border-border gap-0',\n // Outline variant\n variant === 'outline' && 'rounded-lg border border-border gap-0',\n // Sizes — height\n size === 'sm' && 'h-8',\n size === 'md' && 'h-9',\n size === 'lg' && 'h-10',\n className\n )}\n {...props}\n />\n )\n)\nTabsList.displayName = TabsPrimitive.List.displayName\n\n/* -------------------------------------------------------------------------- */\n/* TabsTrigger */\n/* -------------------------------------------------------------------------- */\n\ninterface TabsTriggerProps extends React.ComponentPropsWithoutRef<typeof TabsPrimitive.Trigger> {\n /** Icon rendered before the label */\n icon?: React.ReactNode\n /** Badge/count rendered after the label */\n badge?: React.ReactNode\n}\n\nconst TabsTrigger = React.forwardRef<React.ElementRef<typeof TabsPrimitive.Trigger>, TabsTriggerProps>(\n ({ className, children, icon, badge, ...props }, ref) => (\n <TabsPrimitive.Trigger\n ref={ref}\n data-slot=\"tabs-trigger\"\n className={cn(\n 'inline-flex items-center justify-center gap-1.5 whitespace-nowrap font-medium ring-offset-background transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50',\n // Default (pill) variant\n '[[data-variant=default]_&]:rounded-md [[data-variant=default]_&]:px-3 [[data-variant=default]_&]:data-[state=active]:bg-background [[data-variant=default]_&]:data-[state=active]:text-foreground [[data-variant=default]_&]:data-[state=active]:shadow-sm',\n // Underline variant\n '[[data-variant=underline]_&]:px-4 [[data-variant=underline]_&]:border-b-2 [[data-variant=underline]_&]:border-transparent [[data-variant=underline]_&]:rounded-none [[data-variant=underline]_&]:data-[state=active]:border-brand [[data-variant=underline]_&]:data-[state=active]:text-foreground',\n // Outline variant\n '[[data-variant=outline]_&]:px-4 [[data-variant=outline]_&]:border-e [[data-variant=outline]_&]:border-border [[data-variant=outline]_&]:last:border-e-0 [[data-variant=outline]_&]:first:rounded-s-lg [[data-variant=outline]_&]:last:rounded-e-lg [[data-variant=outline]_&]:data-[state=active]:bg-surface-200 [[data-variant=outline]_&]:data-[state=active]:text-foreground',\n // Sizes\n '[[data-size=sm]_&]:text-xs [[data-size=sm]_&]:h-full',\n '[[data-size=md]_&]:text-sm [[data-size=md]_&]:h-full',\n '[[data-size=lg]_&]:text-base [[data-size=lg]_&]:h-full',\n className\n )}\n {...props}\n >\n {icon && <span className=\"flex-shrink-0\">{icon}</span>}\n {children}\n {badge && (\n <span className=\"inline-flex items-center justify-center min-w-[1.25rem] h-5 px-1 rounded-full bg-surface-300 text-2xs font-semibold\">\n {badge}\n </span>\n )}\n </TabsPrimitive.Trigger>\n )\n)\nTabsTrigger.displayName = TabsPrimitive.Trigger.displayName\n\n/* -------------------------------------------------------------------------- */\n/* TabsContent */\n/* -------------------------------------------------------------------------- */\n\nconst TabsContent = React.forwardRef<\n React.ElementRef<typeof TabsPrimitive.Content>,\n React.ComponentPropsWithoutRef<typeof TabsPrimitive.Content>\n>(({ className, ...props }, ref) => (\n <TabsPrimitive.Content\n ref={ref}\n data-slot=\"tabs-content\"\n className={cn(\n 'mt-2 p-4 ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2',\n className\n )}\n {...props}\n />\n))\nTabsContent.displayName = TabsPrimitive.Content.displayName\n\nexport { Tabs, TabsList, TabsTrigger, TabsContent }\n"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/hooks/use-document-direction.ts","../../../src/lib/utils.ts","../../../src/components/ui/tabs.tsx"],"names":[],"mappings":"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","file":"tabs.js","sourcesContent":["'use client'\n\nimport { useState, useEffect } from 'react'\n\ntype Direction = 'ltr' | 'rtl'\n\nfunction useDocumentDirection(): Direction {\n const [direction, setDirection] = useState<Direction>('rtl')\n\n useEffect(() => {\n const getDirection = (): Direction => {\n if (typeof document === 'undefined') return 'rtl'\n const htmlDir = document.documentElement.getAttribute('dir')\n if (htmlDir === 'ltr' || htmlDir === 'rtl') {\n return htmlDir\n }\n const computedDir = window.getComputedStyle(document.documentElement).direction\n return computedDir === 'rtl' ? 'rtl' : 'ltr'\n }\n\n setDirection(getDirection())\n\n const observer = new MutationObserver(() => {\n setDirection(getDirection())\n })\n\n observer.observe(document.documentElement, {\n attributes: true,\n attributeFilter: ['dir'],\n })\n\n return () => observer.disconnect()\n }, [])\n\n return direction\n}\n\nexport { useDocumentDirection }\nexport type { Direction }\n","import { type ClassValue, clsx } from 'clsx'\nimport { twMerge } from 'tailwind-merge'\nimport { formatJalaliDate } from '@/lib/jalali-utils'\n\nexport function cn(...inputs: ClassValue[]) {\n return twMerge(clsx(inputs))\n}\n\nexport type SupportedLocale = 'fa' | 'ar' | 'en'\n\n/**\n * Convert digits in a string to Persian/Arabic numerals based on locale.\n * @example convertToLocalNumbers('123', 'fa') => '۱۲۳'\n * @example convertToLocalNumbers('123', 'en') => '123'\n */\nexport function convertToLocalNumbers(text: string | number, locale: SupportedLocale): string {\n if (locale === 'fa' || locale === 'ar') {\n const persianDigits = ['۰', '۱', '۲', '۳', '۴', '۵', '۶', '۷', '۸', '۹']\n return String(text).replace(/\\d/g, (digit) => persianDigits[parseInt(digit)])\n }\n return String(text)\n}\n\n/**\n * Format large numbers with locale-aware suffixes (K/M/B).\n * @example formatLargeNumber(1500, 'fa') => '۱.۵ هزار'\n * @example formatLargeNumber(1500, 'en') => '1.5K'\n */\nexport function formatLargeNumber(num: number, locale: SupportedLocale): string {\n if (num >= 1_000_000_000) {\n const formatted = (num / 1_000_000_000).toFixed(1).replace(/\\.0$/, '')\n return convertToLocalNumbers(formatted, locale) + (locale === 'en' ? 'B' : ' میلیارد')\n }\n if (num >= 1_000_000) {\n const formatted = (num / 1_000_000).toFixed(1).replace(/\\.0$/, '')\n return convertToLocalNumbers(formatted, locale) + (locale === 'en' ? 'M' : ' میلیون')\n }\n if (num >= 1_000) {\n const formatted = (num / 1_000).toFixed(1).replace(/\\.0$/, '')\n return convertToLocalNumbers(formatted, locale) + (locale === 'en' ? 'K' : ' هزار')\n }\n return convertToLocalNumbers(num.toString().replace(/\\B(?=(\\d{3})+(?!\\d))/g, ','), locale)\n}\n\n/**\n * Format number to Instagram-style short format (English only).\n * @example formatNumber(123456, 'short') => '123K'\n * @example formatNumber(123456, 'exact') => '123,456'\n */\nexport function formatNumber(num: number | undefined, format: 'exact' | 'short' = 'exact'): string {\n if (num === undefined || num === null) return '0'\n\n if (format === 'exact') {\n return num.toLocaleString('en-US')\n }\n\n // Short format (Instagram style)\n if (num >= 1_000_000_000) {\n return `${(num / 1_000_000_000).toFixed(1).replace(/\\.0$/, '')}B`\n }\n if (num >= 1_000_000) {\n return `${(num / 1_000_000).toFixed(1).replace(/\\.0$/, '')}M`\n }\n if (num >= 1_000) {\n return `${(num / 1_000).toFixed(1).replace(/\\.0$/, '')}K`\n }\n return num.toString()\n}\n\n/**\n * Format date to relative time with absolute on hover (Persian)\n * @example formatRelativeTime(new Date()) => '۲ ساعت پیش'\n */\nexport function formatRelativeTime(date: Date | string | number): string {\n const now = new Date()\n const then = new Date(date)\n const diffInSeconds = Math.floor((now.getTime() - then.getTime()) / 1000)\n\n if (diffInSeconds < 60) {\n return 'همین الان'\n }\n\n const diffInMinutes = Math.floor(diffInSeconds / 60)\n if (diffInMinutes < 60) {\n return `${convertToLocalNumbers(diffInMinutes, 'fa')} دقیقه پیش`\n }\n\n const diffInHours = Math.floor(diffInMinutes / 60)\n if (diffInHours < 24) {\n return `${convertToLocalNumbers(diffInHours, 'fa')} ساعت پیش`\n }\n\n const diffInDays = Math.floor(diffInHours / 24)\n if (diffInDays < 7) {\n return `${convertToLocalNumbers(diffInDays, 'fa')} روز پیش`\n }\n\n const diffInWeeks = Math.floor(diffInDays / 7)\n if (diffInWeeks < 4) {\n return `${convertToLocalNumbers(diffInWeeks, 'fa')} هفته پیش`\n }\n\n const diffInMonths = Math.floor(diffInDays / 30)\n if (diffInMonths < 12) {\n return `${convertToLocalNumbers(diffInMonths, 'fa')} ماه پیش`\n }\n\n const diffInYears = Math.floor(diffInDays / 365)\n return `${convertToLocalNumbers(diffInYears, 'fa')} سال پیش`\n}\n\n/**\n * Format date to absolute format (Persian / Jalali)\n * Uses date-fns-jalali for accurate Jalali conversion.\n * @example formatAbsoluteTime(new Date()) => '۱۵ دی ۱۴۰۳، ۱۵:۳۰'\n */\nexport function formatAbsoluteTime(date: Date | string | number): string {\n const d = new Date(date)\n return formatJalaliDate(d, 'd MMMM yyyy، HH:mm')\n}\n","'use client'\n\nimport * as React from 'react'\nimport * as TabsPrimitive from '@radix-ui/react-tabs'\n\nimport { useDocumentDirection, type Direction } from '@/hooks/use-document-direction'\nimport { cn } from '@/lib/utils'\n\n/* -------------------------------------------------------------------------- */\n/* Tabs Root */\n/* -------------------------------------------------------------------------- */\n\nconst Tabs = React.forwardRef<\n React.ElementRef<typeof TabsPrimitive.Root>,\n React.ComponentPropsWithoutRef<typeof TabsPrimitive.Root>\n>(({ dir, ...props }, ref) => {\n // Forward document `dir` to Radix so ArrowLeft/ArrowRight semantics flip in RTL.\n const documentDir = useDocumentDirection()\n const resolvedDir = (dir as Direction) ?? documentDir\n return <TabsPrimitive.Root ref={ref} dir={resolvedDir} {...props} />\n})\nTabs.displayName = 'Tabs'\n\n/* -------------------------------------------------------------------------- */\n/* TabsList */\n/* -------------------------------------------------------------------------- */\n\ninterface TabsListProps extends React.ComponentPropsWithoutRef<typeof TabsPrimitive.List> {\n /** Visual variant */\n variant?: 'default' | 'underline' | 'outline'\n /** Size */\n size?: 'sm' | 'md' | 'lg'\n}\n\nconst TabsList = React.forwardRef<React.ElementRef<typeof TabsPrimitive.List>, TabsListProps>(\n ({ className, variant = 'default', size = 'md', ...props }, ref) => (\n <TabsPrimitive.List\n ref={ref}\n data-slot=\"tabs-list\"\n data-variant={variant}\n data-size={size}\n className={cn(\n 'inline-flex items-center text-foreground-lighter',\n // Default (pill) variant\n variant === 'default' && 'rounded-lg bg-surface-200 p-1 gap-1',\n // Underline variant\n variant === 'underline' && 'border-b border-border gap-0',\n // Outline variant\n variant === 'outline' && 'rounded-lg border border-border gap-0',\n // Sizes — height\n size === 'sm' && 'h-8',\n size === 'md' && 'h-9',\n size === 'lg' && 'h-10',\n className\n )}\n {...props}\n />\n )\n)\nTabsList.displayName = TabsPrimitive.List.displayName\n\n/* -------------------------------------------------------------------------- */\n/* TabsTrigger */\n/* -------------------------------------------------------------------------- */\n\ninterface TabsTriggerProps extends React.ComponentPropsWithoutRef<typeof TabsPrimitive.Trigger> {\n /** Icon rendered before the label */\n icon?: React.ReactNode\n /** Badge/count rendered after the label */\n badge?: React.ReactNode\n}\n\nconst TabsTrigger = React.forwardRef<React.ElementRef<typeof TabsPrimitive.Trigger>, TabsTriggerProps>(\n ({ className, children, icon, badge, ...props }, ref) => (\n <TabsPrimitive.Trigger\n ref={ref}\n data-slot=\"tabs-trigger\"\n className={cn(\n 'inline-flex items-center justify-center gap-1.5 whitespace-nowrap font-medium ring-offset-background transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50',\n // Default (pill) variant\n '[[data-variant=default]_&]:rounded-md [[data-variant=default]_&]:px-3 [[data-variant=default]_&]:data-[state=active]:bg-background [[data-variant=default]_&]:data-[state=active]:text-foreground [[data-variant=default]_&]:data-[state=active]:shadow-sm',\n // Underline variant\n '[[data-variant=underline]_&]:px-4 [[data-variant=underline]_&]:border-b-2 [[data-variant=underline]_&]:border-transparent [[data-variant=underline]_&]:rounded-none [[data-variant=underline]_&]:data-[state=active]:border-brand [[data-variant=underline]_&]:data-[state=active]:text-foreground',\n // Outline variant\n '[[data-variant=outline]_&]:px-4 [[data-variant=outline]_&]:border-e [[data-variant=outline]_&]:border-border [[data-variant=outline]_&]:last:border-e-0 [[data-variant=outline]_&]:first:rounded-s-lg [[data-variant=outline]_&]:last:rounded-e-lg [[data-variant=outline]_&]:data-[state=active]:bg-surface-200 [[data-variant=outline]_&]:data-[state=active]:text-foreground',\n // Sizes\n '[[data-size=sm]_&]:text-xs [[data-size=sm]_&]:h-full',\n '[[data-size=md]_&]:text-sm [[data-size=md]_&]:h-full',\n '[[data-size=lg]_&]:text-base [[data-size=lg]_&]:h-full',\n className\n )}\n {...props}\n >\n {icon && <span className=\"flex-shrink-0\">{icon}</span>}\n {children}\n {badge && (\n <span className=\"inline-flex items-center justify-center min-w-[1.25rem] h-5 px-1 rounded-full bg-surface-300 text-2xs font-semibold\">\n {badge}\n </span>\n )}\n </TabsPrimitive.Trigger>\n )\n)\nTabsTrigger.displayName = TabsPrimitive.Trigger.displayName\n\n/* -------------------------------------------------------------------------- */\n/* TabsContent */\n/* -------------------------------------------------------------------------- */\n\nconst TabsContent = React.forwardRef<\n React.ElementRef<typeof TabsPrimitive.Content>,\n React.ComponentPropsWithoutRef<typeof TabsPrimitive.Content>\n>(({ className, ...props }, ref) => (\n <TabsPrimitive.Content\n ref={ref}\n data-slot=\"tabs-content\"\n className={cn(\n 'mt-2 p-4 ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2',\n className\n )}\n {...props}\n />\n))\nTabsContent.displayName = TabsPrimitive.Content.displayName\n\nexport { Tabs, TabsList, TabsTrigger, TabsContent }\n"]}
|