@wakastellar/ui 2.0.0 → 2.1.1
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/README.md +71 -8
- package/dist/cli/commands/add.d.ts +7 -0
- package/dist/cli/commands/init.d.ts +6 -0
- package/dist/cli/commands/list.d.ts +5 -0
- package/dist/cli/commands/search.d.ts +1 -0
- package/dist/cli/index.cjs +6014 -0
- package/dist/cli/index.d.ts +1 -0
- package/dist/cli/utils/config.d.ts +29 -0
- package/dist/cli/utils/logger.d.ts +20 -0
- package/dist/cli/utils/registry.d.ts +23 -0
- package/package.json +14 -3
- package/src/blocks/activity-timeline/index.tsx +586 -0
- package/src/blocks/calendar-view/index.tsx +756 -0
- package/src/blocks/chat/index.tsx +1018 -0
- package/src/blocks/chat/widget.tsx +504 -0
- package/src/blocks/dashboard/index.tsx +522 -0
- package/src/blocks/empty-states/index.tsx +452 -0
- package/src/blocks/error-pages/index.tsx +426 -0
- package/src/blocks/faq/index.tsx +479 -0
- package/src/blocks/file-manager/index.tsx +890 -0
- package/src/blocks/footer/index.tsx +133 -0
- package/src/blocks/header/index.tsx +357 -0
- package/src/blocks/headtab/index.tsx +139 -0
- package/src/blocks/i18n-editor/index.tsx +1016 -0
- package/src/blocks/index.ts +80 -0
- package/src/blocks/kanban-board/index.tsx +779 -0
- package/src/blocks/landing/index.tsx +677 -0
- package/src/blocks/language-selector/index.tsx +88 -0
- package/src/blocks/layout/index.tsx +159 -0
- package/src/blocks/login/index.tsx +339 -0
- package/src/blocks/login/types.ts +131 -0
- package/src/blocks/pricing/index.tsx +564 -0
- package/src/blocks/profile/index.tsx +746 -0
- package/src/blocks/settings/index.tsx +558 -0
- package/src/blocks/sidebar/index.tsx +713 -0
- package/src/blocks/theme-creator-block/index.tsx +835 -0
- package/src/blocks/user-management/index.tsx +1037 -0
- package/src/blocks/wizard/index.tsx +719 -0
- package/src/components/DataTable/DataTable.tsx +406 -0
- package/src/components/DataTable/DataTableAdvanced.tsx +720 -0
- package/src/components/DataTable/DataTableBody.tsx +216 -0
- package/src/components/DataTable/DataTableCell.tsx +172 -0
- package/src/components/DataTable/DataTableColumnResizer.tsx +62 -0
- package/src/components/DataTable/DataTableConflictResolver.tsx +478 -0
- package/src/components/DataTable/DataTableContextMenu.tsx +219 -0
- package/src/components/DataTable/DataTableEditCell.tsx +279 -0
- package/src/components/DataTable/DataTableFilterBuilder.tsx +519 -0
- package/src/components/DataTable/DataTableFilters.tsx +535 -0
- package/src/components/DataTable/DataTableGrouping.tsx +147 -0
- package/src/components/DataTable/DataTableHeader.tsx +172 -0
- package/src/components/DataTable/DataTablePagination.tsx +125 -0
- package/src/components/DataTable/DataTableSelection.tsx +269 -0
- package/src/components/DataTable/DataTableSyncStatus.tsx +281 -0
- package/src/components/DataTable/DataTableToolbar.tsx +262 -0
- package/src/components/DataTable/README.md +446 -0
- package/src/components/DataTable/__tests__/DataTableAdvanced.test.tsx +426 -0
- package/src/components/DataTable/__tests__/DataTableEdit.test.tsx +329 -0
- package/src/components/DataTable/__tests__/useDataTableAdvanced.test.ts +455 -0
- package/src/components/DataTable/examples/EditExample.tsx +166 -0
- package/src/components/DataTable/formatters/index.ts +335 -0
- package/src/components/DataTable/hooks/__tests__/useDataTableEdit.test.ts +239 -0
- package/src/components/DataTable/hooks/useDataTable.ts +145 -0
- package/src/components/DataTable/hooks/useDataTableAdvanced.ts +342 -0
- package/src/components/DataTable/hooks/useDataTableAdvancedFilters.ts +637 -0
- package/src/components/DataTable/hooks/useDataTableColumnTemplates.ts +186 -0
- package/src/components/DataTable/hooks/useDataTableEdit.ts +167 -0
- package/src/components/DataTable/hooks/useDataTableExport.ts +227 -0
- package/src/components/DataTable/hooks/useDataTableImport.ts +216 -0
- package/src/components/DataTable/hooks/useDataTableOffline.ts +481 -0
- package/src/components/DataTable/hooks/useDataTableTheme.ts +213 -0
- package/src/components/DataTable/hooks/useDataTableVirtualization.ts +99 -0
- package/src/components/DataTable/hooks/useTableLayout.ts +85 -0
- package/src/components/DataTable/index.ts +81 -0
- package/src/components/DataTable/services/IndexedDBService.ts +504 -0
- package/src/components/DataTable/templates/index.tsx +803 -0
- package/src/components/DataTable/types.ts +504 -0
- package/src/components/DataTable/utils.ts +164 -0
- package/src/components/DataTable/workers/exportWorker.ts +213 -0
- package/src/components/accordion/index.tsx +61 -0
- package/src/components/alert/index.tsx +61 -0
- package/src/components/alert-dialog/index.tsx +146 -0
- package/src/components/aspect-ratio/index.tsx +12 -0
- package/src/components/avatar/index.tsx +54 -0
- package/src/components/badge/Badge.stories.tsx +64 -0
- package/src/components/badge/index.tsx +38 -0
- package/src/components/button/Button.stories.tsx +173 -0
- package/src/components/button/index.tsx +56 -0
- package/src/components/calendar/index.tsx +73 -0
- package/src/components/card/index.tsx +78 -0
- package/src/components/checkbox/index.tsx +34 -0
- package/src/components/code/index.tsx +229 -0
- package/src/components/collapsible/index.tsx +16 -0
- package/src/components/command/index.tsx +162 -0
- package/src/components/context-menu/index.tsx +204 -0
- package/src/components/dialog/index.tsx +126 -0
- package/src/components/dropdown-menu/index.tsx +204 -0
- package/src/components/error-boundary/ErrorBoundary.tsx +281 -0
- package/src/components/error-boundary/index.ts +7 -0
- package/src/components/form/index.tsx +183 -0
- package/src/components/hover-card/index.tsx +33 -0
- package/src/components/index.ts +368 -0
- package/src/components/input/Input.stories.tsx +100 -0
- package/src/components/input/index.tsx +27 -0
- package/src/components/input-otp/index.tsx +277 -0
- package/src/components/label/index.tsx +30 -0
- package/src/components/language-selector/index.tsx +341 -0
- package/src/components/menubar/index.tsx +240 -0
- package/src/components/navigation-menu/index.tsx +134 -0
- package/src/components/popover/index.tsx +35 -0
- package/src/components/progress/index.tsx +32 -0
- package/src/components/radio-group/index.tsx +48 -0
- package/src/components/scroll-area/index.tsx +52 -0
- package/src/components/select/index.tsx +164 -0
- package/src/components/separator/index.tsx +35 -0
- package/src/components/sheet/index.tsx +147 -0
- package/src/components/skeleton/index.tsx +22 -0
- package/src/components/slider/index.tsx +32 -0
- package/src/components/switch/index.tsx +33 -0
- package/src/components/table/index.tsx +117 -0
- package/src/components/tabs/index.tsx +59 -0
- package/src/components/textarea/index.tsx +30 -0
- package/src/components/theme-selector/index.tsx +327 -0
- package/src/components/toast/index.tsx +133 -0
- package/src/components/toaster/index.tsx +34 -0
- package/src/components/toggle/index.tsx +49 -0
- package/src/components/tooltip/index.tsx +34 -0
- package/src/components/typography/index.tsx +276 -0
- package/src/components/waka-3d-pie-chart/index.tsx +486 -0
- package/src/components/waka-achievement-unlock/index.tsx +716 -0
- package/src/components/waka-activity-feed/index.tsx +686 -0
- package/src/components/waka-address-autocomplete/index.tsx +1202 -0
- package/src/components/waka-admincrumb/index.tsx +349 -0
- package/src/components/waka-alert-stack/index.tsx +827 -0
- package/src/components/waka-allocation-matrix/index.tsx +1278 -0
- package/src/components/waka-approval-chain/index.tsx +766 -0
- package/src/components/waka-audit-log/index.tsx +1475 -0
- package/src/components/waka-autocomplete/index.tsx +358 -0
- package/src/components/waka-badge-showcase/index.tsx +704 -0
- package/src/components/waka-barcode/index.tsx +260 -0
- package/src/components/waka-biometric-prompt/index.tsx +765 -0
- package/src/components/waka-bottom-sheet/index.tsx +495 -0
- package/src/components/waka-breadcrumb/index.tsx +376 -0
- package/src/components/waka-breadcrumb-path/index.tsx +513 -0
- package/src/components/waka-budget-burn/index.tsx +1234 -0
- package/src/components/waka-capacity-planner/index.tsx +1107 -0
- package/src/components/waka-carousel/index.tsx +893 -0
- package/src/components/waka-cart-summary/index.tsx +1055 -0
- package/src/components/waka-challenge-timer/index.tsx +1044 -0
- package/src/components/waka-charts/WakaAreaChart.tsx +251 -0
- package/src/components/waka-charts/WakaBarChart.tsx +222 -0
- package/src/components/waka-charts/WakaChart.tsx +124 -0
- package/src/components/waka-charts/WakaLineChart.tsx +219 -0
- package/src/components/waka-charts/WakaMiniChart.tsx +133 -0
- package/src/components/waka-charts/WakaPieChart.tsx +214 -0
- package/src/components/waka-charts/WakaSparkline.tsx +229 -0
- package/src/components/waka-charts/dataTableHelpers.ts +109 -0
- package/src/components/waka-charts/hooks/useChartTheme.ts +123 -0
- package/src/components/waka-charts/hooks/useRechartsLoader.ts +234 -0
- package/src/components/waka-charts/index.ts +90 -0
- package/src/components/waka-charts/types.ts +330 -0
- package/src/components/waka-chat-bubble/index.tsx +1060 -0
- package/src/components/waka-checklist/index.tsx +1067 -0
- package/src/components/waka-checkout-stepper/index.tsx +976 -0
- package/src/components/waka-cohort-table/index.tsx +1011 -0
- package/src/components/waka-color-picker/index.tsx +447 -0
- package/src/components/waka-combo-counter/index.tsx +864 -0
- package/src/components/waka-combobox/index.tsx +497 -0
- package/src/components/waka-command-bar/index.tsx +403 -0
- package/src/components/waka-compare-period/index.tsx +1230 -0
- package/src/components/waka-connection-matrix/index.tsx +1053 -0
- package/src/components/waka-contribution-graph/index.tsx +552 -0
- package/src/components/waka-cost-breakdown/index.tsx +1065 -0
- package/src/components/waka-coupon-input/index.tsx +592 -0
- package/src/components/waka-credit-card-input/index.tsx +982 -0
- package/src/components/waka-daily-reward/index.tsx +762 -0
- package/src/components/waka-date-range-picker/index.tsx +378 -0
- package/src/components/waka-datetime-picker/index.tsx +793 -0
- package/src/components/waka-datetime-picker.form-integration/index.tsx +402 -0
- package/src/components/waka-deployment-lane/index.tsx +673 -0
- package/src/components/waka-device-trust/index.tsx +1259 -0
- package/src/components/waka-dock/index.tsx +285 -0
- package/src/components/waka-drawer/index.tsx +319 -0
- package/src/components/waka-empty-state/index.tsx +545 -0
- package/src/components/waka-error-shake/index.tsx +398 -0
- package/src/components/waka-feature-announcement/index.tsx +991 -0
- package/src/components/waka-file-upload/index.tsx +437 -0
- package/src/components/waka-floating-nav/index.tsx +413 -0
- package/src/components/waka-flow-diagram/index.tsx +508 -0
- package/src/components/waka-funnel-chart/index.tsx +823 -0
- package/src/components/waka-glow-card/index.tsx +246 -0
- package/src/components/waka-goal-progress/index.tsx +1025 -0
- package/src/components/waka-haptic-button/index.tsx +388 -0
- package/src/components/waka-health-pulse/index.tsx +451 -0
- package/src/components/waka-heatmap/index.tsx +1026 -0
- package/src/components/waka-hotspot/index.tsx +682 -0
- package/src/components/waka-image/index.tsx +373 -0
- package/src/components/waka-incident-timeline/index.tsx +686 -0
- package/src/components/waka-invoice-preview/index.tsx +829 -0
- package/src/components/waka-kanban/index.tsx +646 -0
- package/src/components/waka-kpi-dashboard/index.tsx +755 -0
- package/src/components/waka-leaderboard/index.tsx +746 -0
- package/src/components/waka-level-progress/index.tsx +665 -0
- package/src/components/waka-liquid-button/index.tsx +520 -0
- package/src/components/waka-loading-orbit/index.tsx +478 -0
- package/src/components/waka-loot-box/index.tsx +1091 -0
- package/src/components/waka-magic-link/index.tsx +321 -0
- package/src/components/waka-magnetic-button/index.tsx +567 -0
- package/src/components/waka-mention-input/index.tsx +953 -0
- package/src/components/waka-metric-sparkline/index.tsx +627 -0
- package/src/components/waka-milestone-road/index.tsx +1064 -0
- package/src/components/waka-modal/index.tsx +374 -0
- package/src/components/waka-morph-button/index.tsx +495 -0
- package/src/components/waka-network-topology/index.tsx +801 -0
- package/src/components/waka-notifications/index.tsx +414 -0
- package/src/components/waka-number-input/index.tsx +373 -0
- package/src/components/waka-orbital-menu/index.tsx +445 -0
- package/src/components/waka-order-tracker/index.tsx +1041 -0
- package/src/components/waka-pagination/index.tsx +393 -0
- package/src/components/waka-password-strength/index.tsx +824 -0
- package/src/components/waka-payment-method-picker/index.tsx +715 -0
- package/src/components/waka-permission-matrix/index.tsx +1302 -0
- package/src/components/waka-phone-input/index.tsx +801 -0
- package/src/components/waka-pipeline-view/index.tsx +604 -0
- package/src/components/waka-player-card/index.tsx +691 -0
- package/src/components/waka-points-popup/index.tsx +366 -0
- package/src/components/waka-power-up/index.tsx +1155 -0
- package/src/components/waka-presence-indicator/index.tsx +1181 -0
- package/src/components/waka-pricing-table/index.tsx +755 -0
- package/src/components/waka-product-card/index.tsx +786 -0
- package/src/components/waka-progress-onboarding/index.tsx +878 -0
- package/src/components/waka-pull-to-refresh/index.tsx +451 -0
- package/src/components/waka-qrcode/index.tsx +232 -0
- package/src/components/waka-quest-card/index.tsx +1275 -0
- package/src/components/waka-quota-bar/index.tsx +693 -0
- package/src/components/waka-radar-score/index.tsx +512 -0
- package/src/components/waka-rank-badge/index.tsx +813 -0
- package/src/components/waka-rating-input/index.tsx +560 -0
- package/src/components/waka-reaction-picker/index.tsx +1062 -0
- package/src/components/waka-region-map/index.tsx +730 -0
- package/src/components/waka-resource-gauge/index.tsx +654 -0
- package/src/components/waka-resource-pool/index.tsx +1035 -0
- package/src/components/waka-rich-text-editor/index.tsx +594 -0
- package/src/components/waka-rollback-slider/index.tsx +891 -0
- package/src/components/waka-sankey-diagram/index.tsx +1032 -0
- package/src/components/waka-schedule-picker/index.tsx +1060 -0
- package/src/components/waka-scratch-card/index.tsx +914 -0
- package/src/components/waka-season-pass/index.tsx +886 -0
- package/src/components/waka-security-score/index.tsx +1126 -0
- package/src/components/waka-segmented-control/index.tsx +238 -0
- package/src/components/waka-server-rack/index.tsx +764 -0
- package/src/components/waka-session-manager/index.tsx +815 -0
- package/src/components/waka-signature-pad/index.tsx +744 -0
- package/src/components/waka-skeleton-wave/index.tsx +454 -0
- package/src/components/waka-skill-tree/index.tsx +1031 -0
- package/src/components/waka-sla-tracker/index.tsx +798 -0
- package/src/components/waka-slider-range/index.tsx +765 -0
- package/src/components/waka-spin-wheel/index.tsx +671 -0
- package/src/components/waka-spinner/index.tsx +284 -0
- package/src/components/waka-spotlight/index.tsx +410 -0
- package/src/components/waka-stat/index.tsx +428 -0
- package/src/components/waka-stats-hexagon/index.tsx +824 -0
- package/src/components/waka-status-matrix/index.tsx +565 -0
- package/src/components/waka-stepper/index.tsx +489 -0
- package/src/components/waka-streak-counter/index.tsx +334 -0
- package/src/components/waka-success-explosion/index.tsx +453 -0
- package/src/components/waka-swipe-card/index.tsx +574 -0
- package/src/components/waka-tabs-morph/index.tsx +509 -0
- package/src/components/waka-tag-input/index.tsx +877 -0
- package/src/components/waka-team-banner/index.tsx +1183 -0
- package/src/components/waka-terminal-output/index.tsx +836 -0
- package/src/components/waka-theme-creator/index.tsx +762 -0
- package/src/components/waka-theme-manager/index.tsx +654 -0
- package/src/components/waka-thread-view/index.tsx +874 -0
- package/src/components/waka-tilt-card/index.tsx +250 -0
- package/src/components/waka-time-picker/index.tsx +479 -0
- package/src/components/waka-timeline/index.tsx +385 -0
- package/src/components/waka-tooltip-tour/index.tsx +855 -0
- package/src/components/waka-tour-guide/index.tsx +920 -0
- package/src/components/waka-tournament-bracket/index.tsx +1276 -0
- package/src/components/waka-tree/index.tsx +557 -0
- package/src/components/waka-treemap-chart/index.tsx +1031 -0
- package/src/components/waka-two-factor-setup/index.tsx +995 -0
- package/src/components/waka-typewriter/index.tsx +566 -0
- package/src/components/waka-typing-indicator/index.tsx +649 -0
- package/src/components/waka-versus-card/index.tsx +1026 -0
- package/src/components/waka-video/index.tsx +557 -0
- package/src/components/waka-video-call/index.tsx +1087 -0
- package/src/components/waka-virtual-list/index.tsx +327 -0
- package/src/components/waka-voice-message/index.tsx +1019 -0
- package/src/components/waka-welcome-modal/index.tsx +790 -0
- package/src/components/waka-xp-bar/index.tsx +799 -0
|
@@ -0,0 +1,793 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import * as React from "react"
|
|
4
|
+
import DatePicker, { registerLocale } from "react-datepicker"
|
|
5
|
+
import {
|
|
6
|
+
format,
|
|
7
|
+
parse,
|
|
8
|
+
startOfDay,
|
|
9
|
+
endOfDay,
|
|
10
|
+
startOfWeek,
|
|
11
|
+
endOfWeek,
|
|
12
|
+
startOfMonth,
|
|
13
|
+
endOfMonth,
|
|
14
|
+
startOfYear,
|
|
15
|
+
endOfYear,
|
|
16
|
+
subDays,
|
|
17
|
+
subWeeks,
|
|
18
|
+
subMonths,
|
|
19
|
+
addDays,
|
|
20
|
+
addWeeks,
|
|
21
|
+
addMonths,
|
|
22
|
+
isAfter,
|
|
23
|
+
isBefore,
|
|
24
|
+
isEqual,
|
|
25
|
+
isWithinInterval,
|
|
26
|
+
setHours,
|
|
27
|
+
setMinutes,
|
|
28
|
+
setSeconds,
|
|
29
|
+
getHours,
|
|
30
|
+
getMinutes,
|
|
31
|
+
getSeconds,
|
|
32
|
+
} from "date-fns"
|
|
33
|
+
import { fr, enUS, es, de, it, pt, ja, zhCN } from "date-fns/locale"
|
|
34
|
+
import {
|
|
35
|
+
Calendar as CalendarIcon,
|
|
36
|
+
Clock,
|
|
37
|
+
X,
|
|
38
|
+
Check,
|
|
39
|
+
ChevronLeft,
|
|
40
|
+
ChevronRight,
|
|
41
|
+
ChevronsLeft,
|
|
42
|
+
ChevronsRight,
|
|
43
|
+
Plus,
|
|
44
|
+
Minus,
|
|
45
|
+
AlertCircle,
|
|
46
|
+
Info,
|
|
47
|
+
} from "lucide-react"
|
|
48
|
+
import { cn } from "../../utils/cn"
|
|
49
|
+
import { Button } from "../button"
|
|
50
|
+
import { Input } from "../input"
|
|
51
|
+
import { Label } from "../label"
|
|
52
|
+
import { Popover, PopoverContent, PopoverTrigger } from "../popover"
|
|
53
|
+
import { Badge } from "../badge"
|
|
54
|
+
import { Separator } from "../separator"
|
|
55
|
+
import { ScrollArea } from "../scroll-area"
|
|
56
|
+
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "../tooltip"
|
|
57
|
+
import { useTranslation } from "../../hooks/use-translation"
|
|
58
|
+
|
|
59
|
+
// Enregistrer les locales
|
|
60
|
+
registerLocale('fr', fr)
|
|
61
|
+
registerLocale('en', enUS)
|
|
62
|
+
registerLocale('es', es)
|
|
63
|
+
registerLocale('de', de)
|
|
64
|
+
registerLocale('it', it)
|
|
65
|
+
registerLocale('pt', pt)
|
|
66
|
+
registerLocale('ja', ja)
|
|
67
|
+
registerLocale('zh', zhCN)
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Configuration des presets de dates rapides
|
|
71
|
+
*/
|
|
72
|
+
export interface DatePreset {
|
|
73
|
+
label: string
|
|
74
|
+
value?: Date | (() => Date)
|
|
75
|
+
range?: { from: Date; to: Date } | (() => { from: Date; to: Date })
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Configuration de validation
|
|
80
|
+
*/
|
|
81
|
+
export interface DateValidation {
|
|
82
|
+
/** Validation personnalisée */
|
|
83
|
+
validate?: (date: Date) => boolean | string
|
|
84
|
+
/** Message d'erreur personnalisé */
|
|
85
|
+
message?: string
|
|
86
|
+
/** Dates désactivées (patterns) */
|
|
87
|
+
disabledDates?: Date[]
|
|
88
|
+
/** Fonction pour désactiver des dates */
|
|
89
|
+
disabledDatesFn?: (date: Date) => boolean
|
|
90
|
+
/** Jours de la semaine désactivés (0-6, 0 = Dimanche) */
|
|
91
|
+
disabledDaysOfWeek?: number[]
|
|
92
|
+
/** Dates highlightées */
|
|
93
|
+
highlightedDates?: Date[]
|
|
94
|
+
/** Fonction pour highlighter des dates */
|
|
95
|
+
highlightedDatesFn?: (date: Date) => boolean
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Range de dates
|
|
100
|
+
*/
|
|
101
|
+
export interface DateRange {
|
|
102
|
+
from: Date | undefined
|
|
103
|
+
to: Date | undefined
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Props du WakaDateTimePicker
|
|
108
|
+
*/
|
|
109
|
+
export interface WakaDateTimePickerProps {
|
|
110
|
+
/** Valeur actuelle (Date simple ou DateRange) */
|
|
111
|
+
value?: Date | DateRange | Date[] | null
|
|
112
|
+
/** Callback de changement */
|
|
113
|
+
onChange?: (date: Date | DateRange | Date[] | null) => void
|
|
114
|
+
/** Placeholder du champ */
|
|
115
|
+
placeholder?: string
|
|
116
|
+
/** Désactiver le picker */
|
|
117
|
+
disabled?: boolean
|
|
118
|
+
/** Classe CSS supplémentaire */
|
|
119
|
+
className?: string
|
|
120
|
+
/** Variant du picker */
|
|
121
|
+
variant?: "date" | "time" | "datetime" | "range" | "multiple"
|
|
122
|
+
/** Format d'affichage personnalisé */
|
|
123
|
+
format?: string
|
|
124
|
+
/** Date minimum */
|
|
125
|
+
minDate?: Date
|
|
126
|
+
/** Date maximum */
|
|
127
|
+
maxDate?: Date
|
|
128
|
+
/** Afficher les secondes */
|
|
129
|
+
showSeconds?: boolean
|
|
130
|
+
/** Afficher le bouton clear */
|
|
131
|
+
showClear?: boolean
|
|
132
|
+
/** Afficher les presets rapides */
|
|
133
|
+
showPresets?: boolean
|
|
134
|
+
/** Presets personnalisés */
|
|
135
|
+
presets?: DatePreset[]
|
|
136
|
+
/** Locale (fr, en, es, etc.) */
|
|
137
|
+
locale?: string
|
|
138
|
+
/** Format d'heure (12h ou 24h) */
|
|
139
|
+
timeFormat?: "12h" | "24h"
|
|
140
|
+
/** Taille du composant */
|
|
141
|
+
size?: "sm" | "md" | "lg"
|
|
142
|
+
/** Message d'erreur */
|
|
143
|
+
error?: string
|
|
144
|
+
/** Label du champ */
|
|
145
|
+
label?: string
|
|
146
|
+
/** Champ requis */
|
|
147
|
+
required?: boolean
|
|
148
|
+
/** Mode inline (sans popover) */
|
|
149
|
+
inline?: boolean
|
|
150
|
+
/** Nombre de mois à afficher */
|
|
151
|
+
monthsShown?: number
|
|
152
|
+
/** Intervalle de temps (en minutes) */
|
|
153
|
+
timeIntervals?: number
|
|
154
|
+
/** Validation */
|
|
155
|
+
validation?: DateValidation
|
|
156
|
+
/** Afficher les numéros de semaine */
|
|
157
|
+
showWeekNumbers?: boolean
|
|
158
|
+
/** Afficher le bouton "Aujourd'hui" */
|
|
159
|
+
showTodayButton?: boolean
|
|
160
|
+
/** Afficher les infos de sélection */
|
|
161
|
+
showSelectionInfo?: boolean
|
|
162
|
+
/** Autoriser la sélection de plage de temps */
|
|
163
|
+
allowTimeRange?: boolean
|
|
164
|
+
/** Timezone (pour affichage avancé) */
|
|
165
|
+
timezone?: string
|
|
166
|
+
/** Callback avant changement (peut annuler) */
|
|
167
|
+
onBeforeChange?: (date: Date | DateRange | Date[] | null) => boolean
|
|
168
|
+
/** Callback après changement */
|
|
169
|
+
onAfterChange?: (date: Date | DateRange | Date[] | null) => void
|
|
170
|
+
/** Render personnalisé pour une date */
|
|
171
|
+
renderDay?: (date: Date) => React.ReactNode
|
|
172
|
+
/** Classe pour le container du popover */
|
|
173
|
+
popoverClassName?: string
|
|
174
|
+
/** Désactiver les animations */
|
|
175
|
+
disableAnimations?: boolean
|
|
176
|
+
/** Mode lecture seule */
|
|
177
|
+
readOnly?: boolean
|
|
178
|
+
/** ID du champ (pour les tests) */
|
|
179
|
+
id?: string
|
|
180
|
+
/** Name du champ (pour les formulaires) */
|
|
181
|
+
name?: string
|
|
182
|
+
/** Auto-focus */
|
|
183
|
+
autoFocus?: boolean
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* WakaDateTimePicker
|
|
188
|
+
*
|
|
189
|
+
* Composant datetime picker ultra-complet avec support de :
|
|
190
|
+
* - Date, time, datetime, range, multiple selection
|
|
191
|
+
* - Presets rapides
|
|
192
|
+
* - Validation avancée
|
|
193
|
+
* - I18n complet
|
|
194
|
+
* - Thème dynamique
|
|
195
|
+
* - Keyboard navigation
|
|
196
|
+
* - Accessibilité (ARIA)
|
|
197
|
+
* - Time zones
|
|
198
|
+
* - Custom renders
|
|
199
|
+
*/
|
|
200
|
+
export const WakaDateTimePicker = React.forwardRef<HTMLDivElement, WakaDateTimePickerProps>(
|
|
201
|
+
({
|
|
202
|
+
value,
|
|
203
|
+
onChange,
|
|
204
|
+
placeholder,
|
|
205
|
+
disabled = false,
|
|
206
|
+
className,
|
|
207
|
+
variant = "date",
|
|
208
|
+
format: customFormat,
|
|
209
|
+
minDate,
|
|
210
|
+
maxDate,
|
|
211
|
+
showSeconds = false,
|
|
212
|
+
showClear = true,
|
|
213
|
+
showPresets = false,
|
|
214
|
+
presets: customPresets,
|
|
215
|
+
locale = "fr",
|
|
216
|
+
timeFormat = "24h",
|
|
217
|
+
size = "md",
|
|
218
|
+
error,
|
|
219
|
+
label,
|
|
220
|
+
required = false,
|
|
221
|
+
inline = false,
|
|
222
|
+
monthsShown = 1,
|
|
223
|
+
timeIntervals = 15,
|
|
224
|
+
validation,
|
|
225
|
+
showWeekNumbers = false,
|
|
226
|
+
showTodayButton = true,
|
|
227
|
+
showSelectionInfo = false,
|
|
228
|
+
allowTimeRange = false,
|
|
229
|
+
timezone,
|
|
230
|
+
onBeforeChange,
|
|
231
|
+
onAfterChange,
|
|
232
|
+
renderDay,
|
|
233
|
+
popoverClassName,
|
|
234
|
+
disableAnimations = false,
|
|
235
|
+
readOnly = false,
|
|
236
|
+
id,
|
|
237
|
+
name,
|
|
238
|
+
autoFocus = false,
|
|
239
|
+
}, ref) => {
|
|
240
|
+
const { t } = useTranslation()
|
|
241
|
+
const [open, setOpen] = React.useState(false)
|
|
242
|
+
const [internalValue, setInternalValue] = React.useState<Date | DateRange | Date[] | null>(
|
|
243
|
+
variant === "range" && value && !('from' in value) ? { from: value as Date, to: undefined } : value as any
|
|
244
|
+
)
|
|
245
|
+
const [validationError, setValidationError] = React.useState<string | null>(null)
|
|
246
|
+
const [hoveredDate, setHoveredDate] = React.useState<Date | null>(null)
|
|
247
|
+
|
|
248
|
+
// Locales supportées
|
|
249
|
+
const localeMap: Record<string, any> = {
|
|
250
|
+
fr,
|
|
251
|
+
en: enUS,
|
|
252
|
+
es,
|
|
253
|
+
de,
|
|
254
|
+
it,
|
|
255
|
+
pt,
|
|
256
|
+
ja,
|
|
257
|
+
zh: zhCN,
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
const currentLocale = localeMap[locale] || fr
|
|
261
|
+
|
|
262
|
+
// Presets par défaut
|
|
263
|
+
const defaultPresets: DatePreset[] = [
|
|
264
|
+
{
|
|
265
|
+
label: t?.("today") || "Aujourd'hui",
|
|
266
|
+
value: () => new Date(),
|
|
267
|
+
},
|
|
268
|
+
{
|
|
269
|
+
label: t?.("yesterday") || "Hier",
|
|
270
|
+
value: () => subDays(new Date(), 1),
|
|
271
|
+
},
|
|
272
|
+
{
|
|
273
|
+
label: t?.("thisWeek") || "Cette semaine",
|
|
274
|
+
range: () => ({
|
|
275
|
+
from: startOfWeek(new Date(), { locale: currentLocale }),
|
|
276
|
+
to: endOfWeek(new Date(), { locale: currentLocale }),
|
|
277
|
+
}),
|
|
278
|
+
},
|
|
279
|
+
{
|
|
280
|
+
label: t?.("lastWeek") || "Semaine dernière",
|
|
281
|
+
range: () => ({
|
|
282
|
+
from: startOfWeek(subWeeks(new Date(), 1), { locale: currentLocale }),
|
|
283
|
+
to: endOfWeek(subWeeks(new Date(), 1), { locale: currentLocale }),
|
|
284
|
+
}),
|
|
285
|
+
},
|
|
286
|
+
{
|
|
287
|
+
label: t?.("thisMonth") || "Ce mois",
|
|
288
|
+
range: () => ({
|
|
289
|
+
from: startOfMonth(new Date()),
|
|
290
|
+
to: endOfMonth(new Date()),
|
|
291
|
+
}),
|
|
292
|
+
},
|
|
293
|
+
{
|
|
294
|
+
label: t?.("lastMonth") || "Mois dernier",
|
|
295
|
+
range: () => ({
|
|
296
|
+
from: startOfMonth(subMonths(new Date(), 1)),
|
|
297
|
+
to: endOfMonth(subMonths(new Date(), 1)),
|
|
298
|
+
}),
|
|
299
|
+
},
|
|
300
|
+
{
|
|
301
|
+
label: t?.("last7Days") || "7 derniers jours",
|
|
302
|
+
range: () => ({
|
|
303
|
+
from: subDays(new Date(), 7),
|
|
304
|
+
to: new Date(),
|
|
305
|
+
}),
|
|
306
|
+
},
|
|
307
|
+
{
|
|
308
|
+
label: t?.("last30Days") || "30 derniers jours",
|
|
309
|
+
range: () => ({
|
|
310
|
+
from: subDays(new Date(), 30),
|
|
311
|
+
to: new Date(),
|
|
312
|
+
}),
|
|
313
|
+
},
|
|
314
|
+
{
|
|
315
|
+
label: t?.("thisYear") || "Cette année",
|
|
316
|
+
range: () => ({
|
|
317
|
+
from: startOfYear(new Date()),
|
|
318
|
+
to: endOfYear(new Date()),
|
|
319
|
+
}),
|
|
320
|
+
},
|
|
321
|
+
]
|
|
322
|
+
|
|
323
|
+
const activePresets = customPresets || (showPresets ? defaultPresets : [])
|
|
324
|
+
|
|
325
|
+
// Gestion des tailles
|
|
326
|
+
const sizeClasses = {
|
|
327
|
+
sm: "h-8 px-3 text-xs",
|
|
328
|
+
md: "h-10 px-4 text-sm",
|
|
329
|
+
lg: "h-12 px-5 text-base"
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
// Format par défaut selon le variant
|
|
333
|
+
const getDefaultFormat = (): string => {
|
|
334
|
+
switch (variant) {
|
|
335
|
+
case "time":
|
|
336
|
+
return showSeconds
|
|
337
|
+
? (timeFormat === "12h" ? "h:mm:ss a" : "HH:mm:ss")
|
|
338
|
+
: (timeFormat === "12h" ? "h:mm a" : "HH:mm")
|
|
339
|
+
case "datetime":
|
|
340
|
+
return showSeconds
|
|
341
|
+
? (timeFormat === "12h" ? "PPP h:mm:ss a" : "PPP HH:mm:ss")
|
|
342
|
+
: (timeFormat === "12h" ? "PPP h:mm a" : "PPP HH:mm")
|
|
343
|
+
case "range":
|
|
344
|
+
return "PPP"
|
|
345
|
+
case "multiple":
|
|
346
|
+
return "PPP"
|
|
347
|
+
default:
|
|
348
|
+
return "PPP"
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
const displayFormat = customFormat || getDefaultFormat()
|
|
353
|
+
|
|
354
|
+
// Validation d'une date
|
|
355
|
+
const validateDate = (date: Date): string | null => {
|
|
356
|
+
if (!validation) return null
|
|
357
|
+
|
|
358
|
+
// Validation min/max
|
|
359
|
+
if (minDate && isBefore(date, startOfDay(minDate))) {
|
|
360
|
+
return t?.("dateBeforeMin") || `Date antérieure au ${format(minDate, displayFormat, { locale: currentLocale })}`
|
|
361
|
+
}
|
|
362
|
+
if (maxDate && isAfter(date, endOfDay(maxDate))) {
|
|
363
|
+
return t?.("dateAfterMax") || `Date postérieure au ${format(maxDate, displayFormat, { locale: currentLocale })}`
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
// Dates désactivées
|
|
367
|
+
if (validation.disabledDates?.some(d => isEqual(startOfDay(d), startOfDay(date)))) {
|
|
368
|
+
return validation.message || t?.("dateDisabled") || "Date désactivée"
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
// Fonction de désactivation
|
|
372
|
+
if (validation.disabledDatesFn?.(date)) {
|
|
373
|
+
return validation.message || t?.("dateDisabled") || "Date désactivée"
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
// Jours de semaine désactivés
|
|
377
|
+
if (validation.disabledDaysOfWeek?.includes(date.getDay())) {
|
|
378
|
+
return validation.message || t?.("dayOfWeekDisabled") || "Jour de la semaine désactivé"
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
// Validation personnalisée
|
|
382
|
+
if (validation.validate) {
|
|
383
|
+
const result = validation.validate(date)
|
|
384
|
+
if (result === false) {
|
|
385
|
+
return validation.message || t?.("invalidDate") || "Date invalide"
|
|
386
|
+
}
|
|
387
|
+
if (typeof result === "string") {
|
|
388
|
+
return result
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
return null
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
// Gestion du changement de date
|
|
396
|
+
const handleDateChange = (date: Date | [Date, Date] | null) => {
|
|
397
|
+
if (readOnly) return
|
|
398
|
+
|
|
399
|
+
let newValue: Date | DateRange | Date[] | null = null
|
|
400
|
+
|
|
401
|
+
if (variant === "range") {
|
|
402
|
+
if (Array.isArray(date) && date[0] && date[1]) {
|
|
403
|
+
newValue = { from: date[0], to: date[1] }
|
|
404
|
+
} else if (date) {
|
|
405
|
+
// Si une seule date, commence une range
|
|
406
|
+
const currentRange = internalValue as DateRange
|
|
407
|
+
if (currentRange?.from && !currentRange.to) {
|
|
408
|
+
newValue = { from: currentRange.from, to: date as Date }
|
|
409
|
+
} else {
|
|
410
|
+
newValue = { from: date as Date, to: undefined }
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
} else if (variant === "multiple") {
|
|
414
|
+
if (Array.isArray(internalValue)) {
|
|
415
|
+
const dates = [...internalValue]
|
|
416
|
+
const exists = dates.findIndex(d => isEqual(startOfDay(d), startOfDay(date as Date)))
|
|
417
|
+
if (exists >= 0) {
|
|
418
|
+
dates.splice(exists, 1)
|
|
419
|
+
} else {
|
|
420
|
+
dates.push(date as Date)
|
|
421
|
+
}
|
|
422
|
+
newValue = dates
|
|
423
|
+
} else {
|
|
424
|
+
newValue = date ? [date as Date] : []
|
|
425
|
+
}
|
|
426
|
+
} else {
|
|
427
|
+
newValue = date as Date | null
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
// Validation avant changement
|
|
431
|
+
if (onBeforeChange && !onBeforeChange(newValue)) {
|
|
432
|
+
return
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
// Validation
|
|
436
|
+
if (newValue && !(newValue as any).from && !Array.isArray(newValue)) {
|
|
437
|
+
const error = validateDate(newValue as Date)
|
|
438
|
+
if (error) {
|
|
439
|
+
setValidationError(error)
|
|
440
|
+
return
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
setValidationError(null)
|
|
445
|
+
setInternalValue(newValue)
|
|
446
|
+
onChange?.(newValue)
|
|
447
|
+
onAfterChange?.(newValue)
|
|
448
|
+
|
|
449
|
+
// Fermer le popover pour les sélections simples
|
|
450
|
+
if (variant !== "range" && variant !== "multiple" && !inline) {
|
|
451
|
+
setOpen(false)
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
// Appliquer un preset
|
|
456
|
+
const applyPreset = (preset: DatePreset) => {
|
|
457
|
+
if (preset.range) {
|
|
458
|
+
const range = typeof preset.range === "function" ? preset.range() : preset.range
|
|
459
|
+
handleDateChange([range.from, range.to])
|
|
460
|
+
} else if (preset.value) {
|
|
461
|
+
const date = typeof preset.value === "function" ? preset.value() : preset.value
|
|
462
|
+
handleDateChange(date)
|
|
463
|
+
}
|
|
464
|
+
if (!inline) {
|
|
465
|
+
setOpen(false)
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
// Effacer la sélection
|
|
470
|
+
const handleClear = (e?: React.MouseEvent) => {
|
|
471
|
+
if (e) {
|
|
472
|
+
e.stopPropagation()
|
|
473
|
+
}
|
|
474
|
+
if (readOnly) return
|
|
475
|
+
|
|
476
|
+
setInternalValue(null)
|
|
477
|
+
setValidationError(null)
|
|
478
|
+
onChange?.(null)
|
|
479
|
+
onAfterChange?.(null)
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
// Sélectionner aujourd'hui
|
|
483
|
+
const handleToday = () => {
|
|
484
|
+
const today = new Date()
|
|
485
|
+
handleDateChange(today)
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
// Obtenir le texte affiché
|
|
489
|
+
const getDisplayText = (): string => {
|
|
490
|
+
if (!internalValue) return placeholder || t?.("selectDate") || "Sélectionner une date"
|
|
491
|
+
|
|
492
|
+
try {
|
|
493
|
+
if (variant === "range") {
|
|
494
|
+
const range = internalValue as DateRange
|
|
495
|
+
if (range.from && range.to) {
|
|
496
|
+
return `${format(range.from, displayFormat, { locale: currentLocale })} - ${format(range.to, displayFormat, { locale: currentLocale })}`
|
|
497
|
+
}
|
|
498
|
+
if (range.from) {
|
|
499
|
+
return `${format(range.from, displayFormat, { locale: currentLocale })} - ...`
|
|
500
|
+
}
|
|
501
|
+
return placeholder || t?.("selectDateRange") || "Sélectionner une période"
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
if (variant === "multiple") {
|
|
505
|
+
const dates = internalValue as Date[]
|
|
506
|
+
if (dates.length === 0) {
|
|
507
|
+
return placeholder || t?.("selectDates") || "Sélectionner des dates"
|
|
508
|
+
}
|
|
509
|
+
if (dates.length === 1) {
|
|
510
|
+
return format(dates[0], displayFormat, { locale: currentLocale })
|
|
511
|
+
}
|
|
512
|
+
return `${dates.length} ${t?.("datesSelected") || "dates sélectionnées"}`
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
return format(internalValue as Date, displayFormat, { locale: currentLocale })
|
|
516
|
+
} catch (e) {
|
|
517
|
+
console.error("Error formatting date:", e)
|
|
518
|
+
return placeholder || t?.("selectDate") || "Sélectionner une date"
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
// Vérifier si une date est désactivée
|
|
523
|
+
const isDateDisabled = (date: Date): boolean => {
|
|
524
|
+
if (minDate && isBefore(date, startOfDay(minDate))) return true
|
|
525
|
+
if (maxDate && isAfter(date, endOfDay(maxDate))) return true
|
|
526
|
+
if (validation?.disabledDates?.some(d => isEqual(startOfDay(d), startOfDay(date)))) return true
|
|
527
|
+
if (validation?.disabledDatesFn?.(date)) return true
|
|
528
|
+
if (validation?.disabledDaysOfWeek?.includes(date.getDay())) return true
|
|
529
|
+
return false
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
// Vérifier si une date est highlightée
|
|
533
|
+
const isDateHighlighted = (date: Date): boolean => {
|
|
534
|
+
if (validation?.highlightedDates?.some(d => isEqual(startOfDay(d), startOfDay(date)))) return true
|
|
535
|
+
if (validation?.highlightedDatesFn?.(date)) return true
|
|
536
|
+
return false
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
// Render du composant principal
|
|
540
|
+
const renderDatePicker = () => {
|
|
541
|
+
const pickerProps: any = {
|
|
542
|
+
selected: variant === "range"
|
|
543
|
+
? (internalValue as DateRange)?.from
|
|
544
|
+
: variant === "multiple"
|
|
545
|
+
? undefined
|
|
546
|
+
: (internalValue as Date),
|
|
547
|
+
onChange: handleDateChange,
|
|
548
|
+
startDate: variant === "range" ? (internalValue as DateRange)?.from : undefined,
|
|
549
|
+
endDate: variant === "range" ? (internalValue as DateRange)?.to : undefined,
|
|
550
|
+
selectsRange: variant === "range",
|
|
551
|
+
selectsMultiple: variant === "multiple",
|
|
552
|
+
highlightDates: variant === "multiple" ? (internalValue as Date[]) : undefined,
|
|
553
|
+
showTimeSelect: variant === "time" || variant === "datetime",
|
|
554
|
+
showTimeSelectOnly: variant === "time",
|
|
555
|
+
timeFormat: showSeconds
|
|
556
|
+
? (timeFormat === "12h" ? "h:mm:ss aa" : "HH:mm:ss")
|
|
557
|
+
: (timeFormat === "12h" ? "h:mm aa" : "HH:mm"),
|
|
558
|
+
timeIntervals,
|
|
559
|
+
timeCaption: t?.("time") || "Heure",
|
|
560
|
+
dateFormat: displayFormat,
|
|
561
|
+
locale: locale,
|
|
562
|
+
placeholderText: placeholder,
|
|
563
|
+
disabled,
|
|
564
|
+
minDate,
|
|
565
|
+
maxDate,
|
|
566
|
+
inline,
|
|
567
|
+
monthsShown: variant === "range" ? Math.max(2, monthsShown) : monthsShown,
|
|
568
|
+
showWeekNumbers,
|
|
569
|
+
filterDate: (date: Date) => !isDateDisabled(date),
|
|
570
|
+
dayClassName: (date: Date) => {
|
|
571
|
+
const classes = []
|
|
572
|
+
if (isDateHighlighted(date)) {
|
|
573
|
+
classes.push("highlighted-date")
|
|
574
|
+
}
|
|
575
|
+
if (hoveredDate && variant === "range") {
|
|
576
|
+
const range = internalValue as DateRange
|
|
577
|
+
if (range?.from && !range.to && isWithinInterval(date, {
|
|
578
|
+
start: range.from < hoveredDate ? range.from : hoveredDate,
|
|
579
|
+
end: range.from > hoveredDate ? range.from : hoveredDate
|
|
580
|
+
})) {
|
|
581
|
+
classes.push("in-selecting-range")
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
return classes.join(" ")
|
|
585
|
+
},
|
|
586
|
+
onDayMouseEnter: (date: Date) => setHoveredDate(date),
|
|
587
|
+
calendarClassName: cn(
|
|
588
|
+
"waka-datepicker",
|
|
589
|
+
disableAnimations && "no-animations"
|
|
590
|
+
),
|
|
591
|
+
wrapperClassName: "w-full",
|
|
592
|
+
autoFocus,
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
return (
|
|
596
|
+
<div className="flex gap-3">
|
|
597
|
+
{showPresets && activePresets.length > 0 && (
|
|
598
|
+
<div className="flex flex-col gap-1 min-w-[150px] border-r border-border pr-3">
|
|
599
|
+
<Label className="text-xs font-semibold text-muted-foreground mb-1">
|
|
600
|
+
{t?.("quickSelection") || "Sélection rapide"}
|
|
601
|
+
</Label>
|
|
602
|
+
<ScrollArea className="h-[300px]">
|
|
603
|
+
<div className="space-y-1">
|
|
604
|
+
{activePresets.map((preset, index) => (
|
|
605
|
+
<Button
|
|
606
|
+
key={index}
|
|
607
|
+
variant="ghost"
|
|
608
|
+
size="sm"
|
|
609
|
+
className="w-full justify-start text-xs"
|
|
610
|
+
onClick={() => applyPreset(preset)}
|
|
611
|
+
>
|
|
612
|
+
{preset.label}
|
|
613
|
+
</Button>
|
|
614
|
+
))}
|
|
615
|
+
</div>
|
|
616
|
+
</ScrollArea>
|
|
617
|
+
</div>
|
|
618
|
+
)}
|
|
619
|
+
|
|
620
|
+
<div className="flex-1">
|
|
621
|
+
<DatePicker {...pickerProps} />
|
|
622
|
+
|
|
623
|
+
{showSelectionInfo && internalValue && (
|
|
624
|
+
<div className="mt-3 p-2 bg-muted rounded-md text-xs space-y-1">
|
|
625
|
+
<div className="flex items-center gap-2">
|
|
626
|
+
<Info className="h-3 w-3 text-muted-foreground" />
|
|
627
|
+
<span className="font-medium">{t?.("selection") || "Sélection"} :</span>
|
|
628
|
+
</div>
|
|
629
|
+
<div className="text-muted-foreground pl-5">
|
|
630
|
+
{getDisplayText()}
|
|
631
|
+
</div>
|
|
632
|
+
</div>
|
|
633
|
+
)}
|
|
634
|
+
</div>
|
|
635
|
+
</div>
|
|
636
|
+
)
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
// Render inline
|
|
640
|
+
if (inline) {
|
|
641
|
+
return (
|
|
642
|
+
<div ref={ref} className={cn("space-y-2", className)} id={id}>
|
|
643
|
+
{label && (
|
|
644
|
+
<Label className="text-sm font-medium text-foreground">
|
|
645
|
+
{label}
|
|
646
|
+
{required && <span className="text-destructive ml-1">*</span>}
|
|
647
|
+
</Label>
|
|
648
|
+
)}
|
|
649
|
+
|
|
650
|
+
<div className="border border-border rounded-lg p-4 bg-card">
|
|
651
|
+
{renderDatePicker()}
|
|
652
|
+
</div>
|
|
653
|
+
|
|
654
|
+
{(error || validationError) && (
|
|
655
|
+
<div className="flex items-center gap-1.5 text-sm text-destructive">
|
|
656
|
+
<AlertCircle className="h-3.5 w-3.5" />
|
|
657
|
+
<span>{error || validationError}</span>
|
|
658
|
+
</div>
|
|
659
|
+
)}
|
|
660
|
+
</div>
|
|
661
|
+
)
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
// Render avec Popover
|
|
665
|
+
return (
|
|
666
|
+
<div ref={ref} className={cn("space-y-2", className)} id={id}>
|
|
667
|
+
{label && (
|
|
668
|
+
<Label htmlFor={`${id}-input`} className="text-sm font-medium text-foreground">
|
|
669
|
+
{label}
|
|
670
|
+
{required && <span className="text-destructive ml-1">*</span>}
|
|
671
|
+
</Label>
|
|
672
|
+
)}
|
|
673
|
+
|
|
674
|
+
<Popover open={open} onOpenChange={setOpen}>
|
|
675
|
+
<PopoverTrigger asChild>
|
|
676
|
+
<Button
|
|
677
|
+
id={`${id}-input`}
|
|
678
|
+
name={name}
|
|
679
|
+
variant="outline"
|
|
680
|
+
disabled={disabled || readOnly}
|
|
681
|
+
className={cn(
|
|
682
|
+
"w-full justify-between text-left font-normal",
|
|
683
|
+
!internalValue && "text-muted-foreground",
|
|
684
|
+
sizeClasses[size],
|
|
685
|
+
(error || validationError) && "border-destructive focus-visible:ring-destructive",
|
|
686
|
+
readOnly && "cursor-default opacity-70"
|
|
687
|
+
)}
|
|
688
|
+
aria-label={label || placeholder}
|
|
689
|
+
aria-invalid={!!(error || validationError)}
|
|
690
|
+
aria-required={required}
|
|
691
|
+
>
|
|
692
|
+
<span className="flex items-center gap-2 flex-1 truncate">
|
|
693
|
+
{variant === "time" ? (
|
|
694
|
+
<Clock className="h-4 w-4 flex-shrink-0" />
|
|
695
|
+
) : (
|
|
696
|
+
<CalendarIcon className="h-4 w-4 flex-shrink-0" />
|
|
697
|
+
)}
|
|
698
|
+
<span className="truncate">{getDisplayText()}</span>
|
|
699
|
+
</span>
|
|
700
|
+
|
|
701
|
+
<div className="flex items-center gap-1 flex-shrink-0 ml-2">
|
|
702
|
+
{internalValue && showClear && !readOnly && (
|
|
703
|
+
<TooltipProvider>
|
|
704
|
+
<Tooltip>
|
|
705
|
+
<TooltipTrigger asChild>
|
|
706
|
+
<div
|
|
707
|
+
className="h-5 w-5 rounded hover:bg-destructive/10 flex items-center justify-center cursor-pointer"
|
|
708
|
+
onClick={handleClear}
|
|
709
|
+
role="button"
|
|
710
|
+
aria-label={t?.("clear") || "Effacer"}
|
|
711
|
+
>
|
|
712
|
+
<X className="h-3 w-3 text-muted-foreground hover:text-destructive" />
|
|
713
|
+
</div>
|
|
714
|
+
</TooltipTrigger>
|
|
715
|
+
<TooltipContent>
|
|
716
|
+
<p>{t?.("clear") || "Effacer"}</p>
|
|
717
|
+
</TooltipContent>
|
|
718
|
+
</Tooltip>
|
|
719
|
+
</TooltipProvider>
|
|
720
|
+
)}
|
|
721
|
+
|
|
722
|
+
{validationError && (
|
|
723
|
+
<TooltipProvider>
|
|
724
|
+
<Tooltip>
|
|
725
|
+
<TooltipTrigger asChild>
|
|
726
|
+
<AlertCircle className="h-4 w-4 text-destructive" />
|
|
727
|
+
</TooltipTrigger>
|
|
728
|
+
<TooltipContent>
|
|
729
|
+
<p>{validationError}</p>
|
|
730
|
+
</TooltipContent>
|
|
731
|
+
</Tooltip>
|
|
732
|
+
</TooltipProvider>
|
|
733
|
+
)}
|
|
734
|
+
</div>
|
|
735
|
+
</Button>
|
|
736
|
+
</PopoverTrigger>
|
|
737
|
+
|
|
738
|
+
<PopoverContent
|
|
739
|
+
className={cn("w-auto p-0", popoverClassName)}
|
|
740
|
+
align="start"
|
|
741
|
+
sideOffset={4}
|
|
742
|
+
>
|
|
743
|
+
<div className="p-4">
|
|
744
|
+
{renderDatePicker()}
|
|
745
|
+
</div>
|
|
746
|
+
|
|
747
|
+
{(showTodayButton || showClear) && (
|
|
748
|
+
<>
|
|
749
|
+
<Separator />
|
|
750
|
+
<div className="flex items-center justify-between p-3 gap-2">
|
|
751
|
+
{showTodayButton && (
|
|
752
|
+
<Button
|
|
753
|
+
variant="outline"
|
|
754
|
+
size="sm"
|
|
755
|
+
onClick={handleToday}
|
|
756
|
+
disabled={disabled || readOnly}
|
|
757
|
+
>
|
|
758
|
+
<CalendarIcon className="h-3.5 w-3.5 mr-1.5" />
|
|
759
|
+
{t?.("today") || "Aujourd'hui"}
|
|
760
|
+
</Button>
|
|
761
|
+
)}
|
|
762
|
+
|
|
763
|
+
{showClear && internalValue && (
|
|
764
|
+
<Button
|
|
765
|
+
variant="outline"
|
|
766
|
+
size="sm"
|
|
767
|
+
onClick={handleClear}
|
|
768
|
+
disabled={disabled || readOnly}
|
|
769
|
+
className="ml-auto"
|
|
770
|
+
>
|
|
771
|
+
<X className="h-3.5 w-3.5 mr-1.5" />
|
|
772
|
+
{t?.("clear") || "Effacer"}
|
|
773
|
+
</Button>
|
|
774
|
+
)}
|
|
775
|
+
</div>
|
|
776
|
+
</>
|
|
777
|
+
)}
|
|
778
|
+
</PopoverContent>
|
|
779
|
+
</Popover>
|
|
780
|
+
|
|
781
|
+
{(error || validationError) && (
|
|
782
|
+
<div className="flex items-center gap-1.5 text-sm text-destructive">
|
|
783
|
+
<AlertCircle className="h-3.5 w-3.5" />
|
|
784
|
+
<span>{error || validationError}</span>
|
|
785
|
+
</div>
|
|
786
|
+
)}
|
|
787
|
+
</div>
|
|
788
|
+
)
|
|
789
|
+
}
|
|
790
|
+
)
|
|
791
|
+
|
|
792
|
+
WakaDateTimePicker.displayName = "WakaDateTimePicker"
|
|
793
|
+
|