tempest-react-sdk 0.38.2 → 0.38.3
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/bin/lib/design/collect.mjs +19 -0
- package/bin/lib/design/functions.mjs +5 -0
- package/bin/lib/design/index.mjs +8 -3
- package/bin/lib/design/index.test.mjs +31 -0
- package/bin/lib/design/scan.mjs +34 -2
- package/bin/lib/design/scan.test.mjs +78 -0
- package/bin/lib/doctor/doctor.e2e.test.mjs +85 -0
- package/bin/tempest.mjs +90 -6
- package/dist/audio/level-meter.cjs.map +1 -1
- package/dist/audio/level-meter.js.map +1 -1
- package/dist/audio/use-audio-recorder.cjs.map +1 -1
- package/dist/audio/use-audio-recorder.js.map +1 -1
- package/dist/auth/passkey.cjs.map +1 -1
- package/dist/auth/passkey.js.map +1 -1
- package/dist/auth/use-passkey.cjs.map +1 -1
- package/dist/auth/use-passkey.js.map +1 -1
- package/dist/br/BrazilMap.cjs.map +1 -1
- package/dist/br/BrazilMap.js.map +1 -1
- package/dist/br/BrazilStateCitySelect.cjs.map +1 -1
- package/dist/br/BrazilStateCitySelect.js.map +1 -1
- package/dist/br/BrazilStateMap.cjs.map +1 -1
- package/dist/br/BrazilStateMap.js.map +1 -1
- package/dist/br/PixQRCode.cjs.map +1 -1
- package/dist/br/PixQRCode.js.map +1 -1
- package/dist/br/boleto.cjs.map +1 -1
- package/dist/br/boleto.js.map +1 -1
- package/dist/br/pix.cjs.map +1 -1
- package/dist/br/pix.js.map +1 -1
- package/dist/br.d.ts +8 -0
- package/dist/capture/media-recorder.cjs.map +1 -1
- package/dist/capture/media-recorder.js.map +1 -1
- package/dist/capture/use-barcode-scanner.cjs.map +1 -1
- package/dist/capture/use-barcode-scanner.js.map +1 -1
- package/dist/capture/use-screen-capture.cjs.map +1 -1
- package/dist/capture/use-screen-capture.js.map +1 -1
- package/dist/capture/use-speech-recognition.cjs.map +1 -1
- package/dist/capture/use-speech-recognition.js.map +1 -1
- package/dist/capture/use-video-recorder.cjs.map +1 -1
- package/dist/capture/use-video-recorder.js.map +1 -1
- package/dist/charts/types.cjs.map +1 -1
- package/dist/charts/types.js.map +1 -1
- package/dist/charts.d.ts +8 -0
- package/dist/components/AIChat/AIChat.cjs.map +1 -1
- package/dist/components/AIChat/AIChat.js.map +1 -1
- package/dist/components/AIChat/AIChatComposer.cjs.map +1 -1
- package/dist/components/AIChat/AIChatComposer.js.map +1 -1
- package/dist/components/AIChat/AIChatTurn.cjs.map +1 -1
- package/dist/components/AIChat/AIChatTurn.js.map +1 -1
- package/dist/components/AppBar/AppBar.cjs.map +1 -1
- package/dist/components/AppBar/AppBar.js.map +1 -1
- package/dist/components/AudioPlayer/AudioPlayer.cjs.map +1 -1
- package/dist/components/AudioPlayer/AudioPlayer.js.map +1 -1
- package/dist/components/AudioRecorder/AudioRecorder.cjs.map +1 -1
- package/dist/components/AudioRecorder/AudioRecorder.js.map +1 -1
- package/dist/components/BarcodeScanner/BarcodeScanner.cjs.map +1 -1
- package/dist/components/BarcodeScanner/BarcodeScanner.js.map +1 -1
- package/dist/components/Button/Button.cjs.map +1 -1
- package/dist/components/Button/Button.js.map +1 -1
- package/dist/components/Calendar/Calendar.cjs.map +1 -1
- package/dist/components/Calendar/Calendar.js.map +1 -1
- package/dist/components/Carousel/Carousel.cjs.map +1 -1
- package/dist/components/Carousel/Carousel.js.map +1 -1
- package/dist/components/Chat/Chat.cjs.map +1 -1
- package/dist/components/Chat/Chat.js.map +1 -1
- package/dist/components/ChipInput/ChipInput.cjs.map +1 -1
- package/dist/components/ChipInput/ChipInput.js.map +1 -1
- package/dist/components/CodeBlock/CodeBlock.cjs.map +1 -1
- package/dist/components/CodeBlock/CodeBlock.js.map +1 -1
- package/dist/components/Combobox/Combobox.cjs.map +1 -1
- package/dist/components/Combobox/Combobox.js.map +1 -1
- package/dist/components/Command/Command.cjs.map +1 -1
- package/dist/components/Command/Command.js.map +1 -1
- package/dist/components/ConfirmDialog/ConfirmDialog.cjs.map +1 -1
- package/dist/components/ConfirmDialog/ConfirmDialog.js.map +1 -1
- package/dist/components/ContextMenu/ContextMenu.cjs.map +1 -1
- package/dist/components/ContextMenu/ContextMenu.js.map +1 -1
- package/dist/components/CopyButton/CopyButton.cjs.map +1 -1
- package/dist/components/CopyButton/CopyButton.js.map +1 -1
- package/dist/components/DataTable/DataTable.cjs.map +1 -1
- package/dist/components/DataTable/DataTable.js.map +1 -1
- package/dist/components/DataTable/DataTable.module.cjs.map +1 -1
- package/dist/components/DataTable/DataTable.module.js.map +1 -1
- package/dist/components/DataTable/EditableCell.cjs.map +1 -1
- package/dist/components/DataTable/EditableCell.js.map +1 -1
- package/dist/components/DatePicker/DatePicker.cjs.map +1 -1
- package/dist/components/DatePicker/DatePicker.js.map +1 -1
- package/dist/components/DateRangePicker/DateRangePicker.cjs.map +1 -1
- package/dist/components/DateRangePicker/DateRangePicker.js.map +1 -1
- package/dist/components/Drawer/Drawer.cjs.map +1 -1
- package/dist/components/Drawer/Drawer.js.map +1 -1
- package/dist/components/DropdownMenu/DropdownMenu.cjs.map +1 -1
- package/dist/components/DropdownMenu/DropdownMenu.js.map +1 -1
- package/dist/components/Dropzone/Dropzone.cjs.map +1 -1
- package/dist/components/Dropzone/Dropzone.js.map +1 -1
- package/dist/components/FileUpload/FileUpload.cjs.map +1 -1
- package/dist/components/FileUpload/FileUpload.js.map +1 -1
- package/dist/components/FilterBar/FilterBar.cjs.map +1 -1
- package/dist/components/FilterBar/FilterBar.js.map +1 -1
- package/dist/components/ImageCropper/ImageCropper.cjs.map +1 -1
- package/dist/components/ImageCropper/ImageCropper.js.map +1 -1
- package/dist/components/InstallBanner/InstallBanner.cjs.map +1 -1
- package/dist/components/InstallBanner/InstallBanner.js.map +1 -1
- package/dist/components/Kanban/Kanban.cjs.map +1 -1
- package/dist/components/Kanban/Kanban.js.map +1 -1
- package/dist/components/Lightbox/Lightbox.cjs.map +1 -1
- package/dist/components/Lightbox/Lightbox.js.map +1 -1
- package/dist/components/ListTile/ListTile.cjs.map +1 -1
- package/dist/components/ListTile/ListTile.js.map +1 -1
- package/dist/components/Markdown/Markdown.cjs.map +1 -1
- package/dist/components/Markdown/Markdown.js.map +1 -1
- package/dist/components/Markdown/markdown-parse.cjs.map +1 -1
- package/dist/components/Markdown/markdown-parse.js.map +1 -1
- package/dist/components/Menubar/Menubar.cjs.map +1 -1
- package/dist/components/Menubar/Menubar.js.map +1 -1
- package/dist/components/Modal/Modal.cjs.map +1 -1
- package/dist/components/Modal/Modal.js.map +1 -1
- package/dist/components/ModalsManager/ModalsManager.cjs.map +1 -1
- package/dist/components/ModalsManager/ModalsManager.js.map +1 -1
- package/dist/components/MultiSelect/MultiSelect.cjs.map +1 -1
- package/dist/components/MultiSelect/MultiSelect.js.map +1 -1
- package/dist/components/NavigationMenu/NavigationMenu.cjs.map +1 -1
- package/dist/components/NavigationMenu/NavigationMenu.js.map +1 -1
- package/dist/components/NotificationCenter/NotificationCenter.cjs.map +1 -1
- package/dist/components/NotificationCenter/NotificationCenter.js.map +1 -1
- package/dist/components/Page/Page.cjs.map +1 -1
- package/dist/components/Page/Page.js.map +1 -1
- package/dist/components/Pagination/Pagination.cjs.map +1 -1
- package/dist/components/Pagination/Pagination.js.map +1 -1
- package/dist/components/PasswordInput/PasswordInput.cjs.map +1 -1
- package/dist/components/PasswordInput/PasswordInput.js.map +1 -1
- package/dist/components/PinInput/PinInput.cjs.map +1 -1
- package/dist/components/PinInput/PinInput.js.map +1 -1
- package/dist/components/Popover/Popover.cjs.map +1 -1
- package/dist/components/Popover/Popover.js.map +1 -1
- package/dist/components/Progress/Progress.cjs.map +1 -1
- package/dist/components/Progress/Progress.js.map +1 -1
- package/dist/components/QRCode/qr-encode.cjs.map +1 -1
- package/dist/components/QRCode/qr-encode.js.map +1 -1
- package/dist/components/Radio/Radio.cjs.map +1 -1
- package/dist/components/Radio/Radio.js.map +1 -1
- package/dist/components/RangeSlider/RangeSlider.cjs.map +1 -1
- package/dist/components/RangeSlider/RangeSlider.js.map +1 -1
- package/dist/components/RatingStars/RatingStars.cjs.map +1 -1
- package/dist/components/RatingStars/RatingStars.js.map +1 -1
- package/dist/components/RefreshIndicator/RefreshIndicator.cjs.map +1 -1
- package/dist/components/RefreshIndicator/RefreshIndicator.js.map +1 -1
- package/dist/components/Resizable/Resizable.cjs.map +1 -1
- package/dist/components/Resizable/Resizable.js.map +1 -1
- package/dist/components/Scheduler/Scheduler.cjs.map +1 -1
- package/dist/components/Scheduler/Scheduler.js.map +1 -1
- package/dist/components/Sidebar/Sidebar.cjs.map +1 -1
- package/dist/components/Sidebar/Sidebar.js.map +1 -1
- package/dist/components/SignaturePad/SignaturePad.cjs.map +1 -1
- package/dist/components/SignaturePad/SignaturePad.js.map +1 -1
- package/dist/components/Slider/Slider.cjs.map +1 -1
- package/dist/components/Slider/Slider.js.map +1 -1
- package/dist/components/Sparkline/Sparkline.cjs.map +1 -1
- package/dist/components/Sparkline/Sparkline.js.map +1 -1
- package/dist/components/StepperInput/StepperInput.cjs.map +1 -1
- package/dist/components/StepperInput/StepperInput.js.map +1 -1
- package/dist/components/Table/Table.cjs.map +1 -1
- package/dist/components/Table/Table.js.map +1 -1
- package/dist/components/TimePicker/TimePicker.cjs.map +1 -1
- package/dist/components/TimePicker/TimePicker.js.map +1 -1
- package/dist/components/Toast/ToastProvider.cjs.map +1 -1
- package/dist/components/Toast/ToastProvider.js.map +1 -1
- package/dist/components/Tour/Tour.cjs.map +1 -1
- package/dist/components/Tour/Tour.js.map +1 -1
- package/dist/components/Transfer/Transfer.cjs.map +1 -1
- package/dist/components/Transfer/Transfer.js.map +1 -1
- package/dist/components/TreeView/TreeView.cjs.map +1 -1
- package/dist/components/TreeView/TreeView.js.map +1 -1
- package/dist/components/VirtualList/VirtualList.cjs.map +1 -1
- package/dist/components/VirtualList/VirtualList.js.map +1 -1
- package/dist/components/VirtualTable/VirtualTable.cjs.map +1 -1
- package/dist/components/VirtualTable/VirtualTable.js.map +1 -1
- package/dist/components/Wizard/Wizard.cjs.map +1 -1
- package/dist/components/Wizard/Wizard.js.map +1 -1
- package/dist/editor/RichTextEditor.cjs.map +1 -1
- package/dist/editor/RichTextEditor.js.map +1 -1
- package/dist/forms/FormField.cjs.map +1 -1
- package/dist/forms/FormField.js.map +1 -1
- package/dist/geo/TrajectoryMap.cjs.map +1 -1
- package/dist/geo/TrajectoryMap.js.map +1 -1
- package/dist/geo/estimate.cjs.map +1 -1
- package/dist/geo/estimate.js.map +1 -1
- package/dist/geo/projection.cjs.map +1 -1
- package/dist/geo/projection.js.map +1 -1
- package/dist/hooks/use-event-listener.cjs.map +1 -1
- package/dist/hooks/use-event-listener.js.map +1 -1
- package/dist/hooks/use-local-storage.cjs.map +1 -1
- package/dist/hooks/use-local-storage.js.map +1 -1
- package/dist/hooks/use-sortable.cjs.map +1 -1
- package/dist/hooks/use-sortable.js.map +1 -1
- package/dist/http/api-client.cjs.map +1 -1
- package/dist/http/api-client.js.map +1 -1
- package/dist/http/errors.cjs.map +1 -1
- package/dist/http/errors.js.map +1 -1
- package/dist/http/resumable-upload.cjs.map +1 -1
- package/dist/http/resumable-upload.js.map +1 -1
- package/dist/http/upload-with-progress.cjs.map +1 -1
- package/dist/http/upload-with-progress.js.map +1 -1
- package/dist/i18n/I18nProvider.cjs.map +1 -1
- package/dist/i18n/I18nProvider.js.map +1 -1
- package/dist/imaging/canvas.cjs.map +1 -1
- package/dist/imaging/canvas.js.map +1 -1
- package/dist/imaging.d.ts +5 -0
- package/dist/logger/logger.cjs.map +1 -1
- package/dist/logger/logger.js.map +1 -1
- package/dist/oauth/GoogleSignIn.cjs.map +1 -1
- package/dist/oauth/GoogleSignIn.js.map +1 -1
- package/dist/offline/create-offline-sync.cjs.map +1 -1
- package/dist/offline/create-offline-sync.js.map +1 -1
- package/dist/perf/cache-size.cjs +1 -1
- package/dist/perf/cache-size.cjs.map +1 -1
- package/dist/perf/cache-size.js +27 -6
- package/dist/perf/cache-size.js.map +1 -1
- package/dist/sse/create-event-stream.cjs.map +1 -1
- package/dist/sse/create-event-stream.js.map +1 -1
- package/dist/styles.css +1 -1
- package/dist/sw/background-sync.cjs.map +1 -1
- package/dist/sw/background-sync.js.map +1 -1
- package/dist/sw/cache.cjs.map +1 -1
- package/dist/sw/cache.js.map +1 -1
- package/dist/sw.d.ts +12 -0
- package/dist/tabular/compact.cjs.map +1 -1
- package/dist/tabular/compact.js.map +1 -1
- package/dist/tabular/predictor.cjs.map +1 -1
- package/dist/tabular/predictor.js.map +1 -1
- package/dist/tabular.d.ts +7 -2
- package/dist/tempest-react-sdk.d.ts +133 -6
- package/dist/theme/ThemeProvider.cjs.map +1 -1
- package/dist/theme/ThemeProvider.js.map +1 -1
- package/dist/theme/color.cjs.map +1 -1
- package/dist/theme/color.js.map +1 -1
- package/dist/theme/create-theme.cjs.map +1 -1
- package/dist/theme/create-theme.js.map +1 -1
- package/dist/theme/data-viz-ramps.cjs +1 -1
- package/dist/theme/data-viz-ramps.cjs.map +1 -1
- package/dist/theme/data-viz-ramps.js +1 -1
- package/dist/theme/data-viz-ramps.js.map +1 -1
- package/dist/utils/storage.cjs.map +1 -1
- package/dist/utils/storage.js.map +1 -1
- package/dist/vision/core/canvas.cjs.map +1 -1
- package/dist/vision/core/canvas.js.map +1 -1
- package/dist/vision/core/exceptions.cjs.map +1 -1
- package/dist/vision/core/exceptions.js.map +1 -1
- package/dist/vision/core/graph.cjs.map +1 -1
- package/dist/vision/core/graph.js.map +1 -1
- package/dist/vision/core/metadata.cjs.map +1 -1
- package/dist/vision/core/metadata.js.map +1 -1
- package/dist/vision/core/providers.cjs.map +1 -1
- package/dist/vision/core/providers.js.map +1 -1
- package/dist/vision/core/session.cjs.map +1 -1
- package/dist/vision/core/session.js.map +1 -1
- package/dist/vision/core/timing.cjs.map +1 -1
- package/dist/vision/core/timing.js.map +1 -1
- package/dist/vision/io/image.cjs.map +1 -1
- package/dist/vision/io/image.js.map +1 -1
- package/dist/vision/labels.cjs.map +1 -1
- package/dist/vision/labels.js.map +1 -1
- package/dist/vision/luminance.cjs.map +1 -1
- package/dist/vision/luminance.js.map +1 -1
- package/dist/vision/postprocess/classification.cjs.map +1 -1
- package/dist/vision/postprocess/classification.js.map +1 -1
- package/dist/vision/postprocess/detection.cjs.map +1 -1
- package/dist/vision/postprocess/detection.js.map +1 -1
- package/dist/vision/postprocess/segmentation.cjs.map +1 -1
- package/dist/vision/postprocess/segmentation.js.map +1 -1
- package/dist/vision/preprocess/image.cjs.map +1 -1
- package/dist/vision/preprocess/image.js.map +1 -1
- package/dist/vision/results.cjs.map +1 -1
- package/dist/vision/results.js.map +1 -1
- package/dist/vision/tasks/base.cjs.map +1 -1
- package/dist/vision/tasks/base.js.map +1 -1
- package/dist/vision/tasks/classifier.cjs.map +1 -1
- package/dist/vision/tasks/classifier.js.map +1 -1
- package/dist/vision/tasks/detector.cjs.map +1 -1
- package/dist/vision/tasks/detector.js.map +1 -1
- package/dist/vision/tasks/segmenter.cjs.map +1 -1
- package/dist/vision/tasks/segmenter.js.map +1 -1
- package/dist/vision/types.cjs.map +1 -1
- package/dist/vision/types.js.map +1 -1
- package/dist/vision.d.ts +6 -0
- package/dist/vite/tempest-icons.cjs.map +1 -1
- package/dist/vite/tempest-icons.js.map +1 -1
- package/dist/vite/tempest-pwa-icons.cjs.map +1 -1
- package/dist/vite/tempest-pwa-icons.js.map +1 -1
- package/dist/ws/create-web-socket.cjs.map +1 -1
- package/dist/ws/create-web-socket.js.map +1 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DateRangePicker.cjs","names":[],"sources":["../../../src/components/DateRangePicker/DateRangePicker.tsx"],"sourcesContent":["import { useCallback, useMemo, useState } from \"react\";\nimport type { HTMLAttributes } from \"react\";\nimport { cn } from \"@/utils/cn\";\nimport styles from \"./DateRangePicker.module.css\";\n\nexport type WeekStart = 0 | 1;\n\n/** A selected range. Either bound may be `null` while picking. */\nexport interface DateRange {\n start: Date | null;\n end: Date | null;\n}\n\nexport interface DateRangePickerProps extends Omit<\n HTMLAttributes<HTMLDivElement>,\n \"onChange\" | \"defaultValue\"\n> {\n /** Controlled selected range. */\n value: DateRange;\n /** Called with the new range as the user picks start then end. */\n onChange: (range: DateRange) => void;\n /** How many month grids to show side by side. Default `2`. */\n numberOfMonths?: number;\n /** Initial visible month (uncontrolled). Defaults to the range start or today. */\n defaultMonth?: Date;\n minDate?: Date;\n maxDate?: Date;\n /** First column of the week — `0` Sunday (default) or `1` Monday. */\n weekStartsOn?: WeekStart;\n}\n\nconst WEEKDAY_LABELS = [\"Sun\", \"Mon\", \"Tue\", \"Wed\", \"Thu\", \"Fri\", \"Sat\"];\nconst MONTH_LABELS = [\n \"January\",\n \"February\",\n \"March\",\n \"April\",\n \"May\",\n \"June\",\n \"July\",\n \"August\",\n \"September\",\n \"October\",\n \"November\",\n \"December\",\n];\nconst DAYS_IN_GRID = 42;\n\nfunction makeDate(year: number, month: number, day: number): Date {\n return new Date(year, month, day);\n}\n\nfunction startOfDay(date: Date): Date {\n return new Date(date.getFullYear(), date.getMonth(), date.getDate());\n}\n\nfunction addMonths(date: Date, offset: number): Date {\n return makeDate(date.getFullYear(), date.getMonth() + offset, 1);\n}\n\nfunction isSameDay(a: Date, b: Date): boolean {\n return (\n a.getFullYear() === b.getFullYear() &&\n a.getMonth() === b.getMonth() &&\n a.getDate() === b.getDate()\n );\n}\n\nfunction isOutOfRange(date: Date, minDate?: Date, maxDate?: Date): boolean {\n const day = startOfDay(date).getTime();\n if (minDate && day < startOfDay(minDate).getTime()) return true;\n if (maxDate && day > startOfDay(maxDate).getTime()) return true;\n return false;\n}\n\n/** Inclusive-exclusive-agnostic \"strictly between a and b\" (a/b order-independent). */\nfunction isBetween(date: Date, a: Date, b: Date): boolean {\n const t = startOfDay(date).getTime();\n const lo = Math.min(startOfDay(a).getTime(), startOfDay(b).getTime());\n const hi = Math.max(startOfDay(a).getTime(), startOfDay(b).getTime());\n return t > lo && t < hi;\n}\n\nfunction buildGrid(month: Date, weekStartsOn: WeekStart): Date[] {\n const year = month.getFullYear();\n const monthIndex = month.getMonth();\n const firstWeekday = makeDate(year, monthIndex, 1).getDay();\n const leading = (firstWeekday - weekStartsOn + 7) % 7;\n const start = makeDate(year, monthIndex, 1 - leading);\n return Array.from({ length: DAYS_IN_GRID }, (_, i) =>\n makeDate(start.getFullYear(), start.getMonth(), start.getDate() + i),\n );\n}\n\n/**\n * Date-range picker: pick a start day, then an end day, across one or more\n * month grids. The hovered day previews the range before the end is committed.\n * Pure `Date` math — no date libraries. For a single date, use `Calendar`.\n *\n * Selection: first click sets `start` (clears `end`); the next click sets `end`\n * (auto-ordered if you click earlier than the start); a third click starts over.\n */\nexport function DateRangePicker({\n value,\n onChange,\n numberOfMonths = 2,\n defaultMonth,\n minDate,\n maxDate,\n weekStartsOn = 0,\n className,\n ...props\n}: DateRangePickerProps) {\n const [visibleMonth, setVisibleMonth] = useState<Date>(() => {\n const base = defaultMonth ?? value.start ?? new Date();\n return makeDate(base.getFullYear(), base.getMonth(), 1);\n });\n const [hovered, setHovered] = useState<Date | null>(null);\n\n const today = useMemo(() => startOfDay(new Date()), []);\n const weekdayLabels = useMemo(\n () => Array.from({ length: 7 }, (_, i) => WEEKDAY_LABELS[(i + weekStartsOn) % 7]),\n [weekStartsOn],\n );\n\n const selectDate = useCallback(\n (date: Date): void => {\n if (isOutOfRange(date, minDate, maxDate)) return;\n const { start, end } = value;\n if (!start || end) {\n onChange({ start: date, end: null });\n } else if (startOfDay(date).getTime() < startOfDay(start).getTime()) {\n onChange({ start: date, end: start });\n } else {\n onChange({ start, end: date });\n }\n },\n [value, onChange, minDate, maxDate],\n );\n\n const changeMonth = useCallback((offset: number): void => {\n setVisibleMonth((current) => addMonths(current, offset));\n }, []);\n\n // Effective end used for preview highlighting (hovered day while picking).\n const previewEnd = value.start && !value.end ? hovered : value.end;\n\n function dayState(date: Date): {\n selected: boolean;\n inRange: boolean;\n isStart: boolean;\n isEnd: boolean;\n } {\n const { start } = value;\n const end = previewEnd;\n const isStart = start ? isSameDay(date, start) : false;\n const isEnd = end ? isSameDay(date, end) : false;\n const inRange = start && end ? isBetween(date, start, end) : false;\n return { selected: isStart || isEnd, inRange, isStart, isEnd };\n }\n\n return (\n <div className={cn(styles.root, className)} {...props}>\n <div className={styles.header}>\n <button\n type=\"button\"\n className={styles.nav}\n aria-label=\"Previous month\"\n onClick={() => changeMonth(-1)}\n >\n {\"‹\"}\n </button>\n <span className={styles.headerSpacer} aria-hidden />\n <button\n type=\"button\"\n className={styles.nav}\n aria-label=\"Next month\"\n onClick={() => changeMonth(1)}\n >\n {\"›\"}\n </button>\n </div>\n\n <div className={styles.months} onMouseLeave={() => setHovered(null)}>\n {Array.from({ length: Math.max(1, numberOfMonths) }, (_, monthOffset) => {\n const month = addMonths(visibleMonth, monthOffset);\n const cells = buildGrid(month, weekStartsOn);\n return (\n <div key={monthOffset} className={styles.month}>\n <div className={styles.title} aria-live=\"polite\">\n {MONTH_LABELS[month.getMonth()]} {month.getFullYear()}\n </div>\n <div className={styles.weekdays} role=\"row\">\n {weekdayLabels.map((label) => (\n <span\n key={label}\n className={styles.weekday}\n role=\"columnheader\"\n >\n {label}\n </span>\n ))}\n </div>\n <div className={styles.grid} role=\"grid\">\n {cells.map((date, index) => {\n const inMonth = date.getMonth() === month.getMonth();\n const disabled = isOutOfRange(date, minDate, maxDate);\n const { selected, inRange, isStart, isEnd } = dayState(date);\n const isToday = isSameDay(date, today);\n return (\n <button\n key={index}\n type=\"button\"\n role=\"gridcell\"\n className={cn(\n styles.day,\n !inMonth && styles.outside,\n inRange && styles.inRange,\n selected && styles.selected,\n isStart && styles.rangeStart,\n isEnd && styles.rangeEnd,\n isToday && styles.today,\n )}\n aria-label={`${MONTH_LABELS[date.getMonth()]} ${date.getDate()}, ${date.getFullYear()}`}\n aria-selected={selected}\n aria-current={isToday ? \"date\" : undefined}\n disabled={disabled}\n onClick={() => selectDate(date)}\n onMouseEnter={() => setHovered(date)}\n >\n {date.getDate()}\n </button>\n );\n })}\n </div>\n </div>\n );\n })}\n </div>\n </div>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"DateRangePicker.cjs","names":[],"sources":["../../../src/components/DateRangePicker/DateRangePicker.tsx"],"sourcesContent":["/**\n * @tempest-limits file-lines, function-lines — a range is two dates whose validity\n * depends on each other, so the body keeps both ends, the hovered preview end and\n * the open panel in one place; splitting the halves would mean each one reaching\n * into the other's state to clamp itself.\n */\nimport { useCallback, useMemo, useState } from \"react\";\nimport type { HTMLAttributes } from \"react\";\nimport { cn } from \"@/utils/cn\";\nimport styles from \"./DateRangePicker.module.css\";\n\nexport type WeekStart = 0 | 1;\n\n/** A selected range. Either bound may be `null` while picking. */\nexport interface DateRange {\n start: Date | null;\n end: Date | null;\n}\n\nexport interface DateRangePickerProps extends Omit<\n HTMLAttributes<HTMLDivElement>,\n \"onChange\" | \"defaultValue\"\n> {\n /** Controlled selected range. */\n value: DateRange;\n /** Called with the new range as the user picks start then end. */\n onChange: (range: DateRange) => void;\n /** How many month grids to show side by side. Default `2`. */\n numberOfMonths?: number;\n /** Initial visible month (uncontrolled). Defaults to the range start or today. */\n defaultMonth?: Date;\n minDate?: Date;\n maxDate?: Date;\n /** First column of the week — `0` Sunday (default) or `1` Monday. */\n weekStartsOn?: WeekStart;\n}\n\nconst WEEKDAY_LABELS = [\"Sun\", \"Mon\", \"Tue\", \"Wed\", \"Thu\", \"Fri\", \"Sat\"];\nconst MONTH_LABELS = [\n \"January\",\n \"February\",\n \"March\",\n \"April\",\n \"May\",\n \"June\",\n \"July\",\n \"August\",\n \"September\",\n \"October\",\n \"November\",\n \"December\",\n];\nconst DAYS_IN_GRID = 42;\n\nfunction makeDate(year: number, month: number, day: number): Date {\n return new Date(year, month, day);\n}\n\nfunction startOfDay(date: Date): Date {\n return new Date(date.getFullYear(), date.getMonth(), date.getDate());\n}\n\nfunction addMonths(date: Date, offset: number): Date {\n return makeDate(date.getFullYear(), date.getMonth() + offset, 1);\n}\n\nfunction isSameDay(a: Date, b: Date): boolean {\n return (\n a.getFullYear() === b.getFullYear() &&\n a.getMonth() === b.getMonth() &&\n a.getDate() === b.getDate()\n );\n}\n\nfunction isOutOfRange(date: Date, minDate?: Date, maxDate?: Date): boolean {\n const day = startOfDay(date).getTime();\n if (minDate && day < startOfDay(minDate).getTime()) return true;\n if (maxDate && day > startOfDay(maxDate).getTime()) return true;\n return false;\n}\n\n/** Inclusive-exclusive-agnostic \"strictly between a and b\" (a/b order-independent). */\nfunction isBetween(date: Date, a: Date, b: Date): boolean {\n const t = startOfDay(date).getTime();\n const lo = Math.min(startOfDay(a).getTime(), startOfDay(b).getTime());\n const hi = Math.max(startOfDay(a).getTime(), startOfDay(b).getTime());\n return t > lo && t < hi;\n}\n\nfunction buildGrid(month: Date, weekStartsOn: WeekStart): Date[] {\n const year = month.getFullYear();\n const monthIndex = month.getMonth();\n const firstWeekday = makeDate(year, monthIndex, 1).getDay();\n const leading = (firstWeekday - weekStartsOn + 7) % 7;\n const start = makeDate(year, monthIndex, 1 - leading);\n return Array.from({ length: DAYS_IN_GRID }, (_, i) =>\n makeDate(start.getFullYear(), start.getMonth(), start.getDate() + i),\n );\n}\n\n/**\n * Date-range picker: pick a start day, then an end day, across one or more\n * month grids. The hovered day previews the range before the end is committed.\n * Pure `Date` math — no date libraries. For a single date, use `Calendar`.\n *\n * Selection: first click sets `start` (clears `end`); the next click sets `end`\n * (auto-ordered if you click earlier than the start); a third click starts over.\n */\nexport function DateRangePicker({\n value,\n onChange,\n numberOfMonths = 2,\n defaultMonth,\n minDate,\n maxDate,\n weekStartsOn = 0,\n className,\n ...props\n}: DateRangePickerProps) {\n const [visibleMonth, setVisibleMonth] = useState<Date>(() => {\n const base = defaultMonth ?? value.start ?? new Date();\n return makeDate(base.getFullYear(), base.getMonth(), 1);\n });\n const [hovered, setHovered] = useState<Date | null>(null);\n\n const today = useMemo(() => startOfDay(new Date()), []);\n const weekdayLabels = useMemo(\n () => Array.from({ length: 7 }, (_, i) => WEEKDAY_LABELS[(i + weekStartsOn) % 7]),\n [weekStartsOn],\n );\n\n const selectDate = useCallback(\n (date: Date): void => {\n if (isOutOfRange(date, minDate, maxDate)) return;\n const { start, end } = value;\n if (!start || end) {\n onChange({ start: date, end: null });\n } else if (startOfDay(date).getTime() < startOfDay(start).getTime()) {\n onChange({ start: date, end: start });\n } else {\n onChange({ start, end: date });\n }\n },\n [value, onChange, minDate, maxDate],\n );\n\n const changeMonth = useCallback((offset: number): void => {\n setVisibleMonth((current) => addMonths(current, offset));\n }, []);\n\n // Effective end used for preview highlighting (hovered day while picking).\n const previewEnd = value.start && !value.end ? hovered : value.end;\n\n function dayState(date: Date): {\n selected: boolean;\n inRange: boolean;\n isStart: boolean;\n isEnd: boolean;\n } {\n const { start } = value;\n const end = previewEnd;\n const isStart = start ? isSameDay(date, start) : false;\n const isEnd = end ? isSameDay(date, end) : false;\n const inRange = start && end ? isBetween(date, start, end) : false;\n return { selected: isStart || isEnd, inRange, isStart, isEnd };\n }\n\n return (\n <div className={cn(styles.root, className)} {...props}>\n <div className={styles.header}>\n <button\n type=\"button\"\n className={styles.nav}\n aria-label=\"Previous month\"\n onClick={() => changeMonth(-1)}\n >\n {\"‹\"}\n </button>\n <span className={styles.headerSpacer} aria-hidden />\n <button\n type=\"button\"\n className={styles.nav}\n aria-label=\"Next month\"\n onClick={() => changeMonth(1)}\n >\n {\"›\"}\n </button>\n </div>\n\n <div className={styles.months} onMouseLeave={() => setHovered(null)}>\n {Array.from({ length: Math.max(1, numberOfMonths) }, (_, monthOffset) => {\n const month = addMonths(visibleMonth, monthOffset);\n const cells = buildGrid(month, weekStartsOn);\n return (\n <div key={monthOffset} className={styles.month}>\n <div className={styles.title} aria-live=\"polite\">\n {MONTH_LABELS[month.getMonth()]} {month.getFullYear()}\n </div>\n <div className={styles.weekdays} role=\"row\">\n {weekdayLabels.map((label) => (\n <span\n key={label}\n className={styles.weekday}\n role=\"columnheader\"\n >\n {label}\n </span>\n ))}\n </div>\n <div className={styles.grid} role=\"grid\">\n {cells.map((date, index) => {\n const inMonth = date.getMonth() === month.getMonth();\n const disabled = isOutOfRange(date, minDate, maxDate);\n const { selected, inRange, isStart, isEnd } = dayState(date);\n const isToday = isSameDay(date, today);\n return (\n <button\n key={index}\n type=\"button\"\n role=\"gridcell\"\n className={cn(\n styles.day,\n !inMonth && styles.outside,\n inRange && styles.inRange,\n selected && styles.selected,\n isStart && styles.rangeStart,\n isEnd && styles.rangeEnd,\n isToday && styles.today,\n )}\n aria-label={`${MONTH_LABELS[date.getMonth()]} ${date.getDate()}, ${date.getFullYear()}`}\n aria-selected={selected}\n aria-current={isToday ? \"date\" : undefined}\n disabled={disabled}\n onClick={() => selectDate(date)}\n onMouseEnter={() => setHovered(date)}\n >\n {date.getDate()}\n </button>\n );\n })}\n </div>\n </div>\n );\n })}\n </div>\n </div>\n );\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DateRangePicker.js","names":[],"sources":["../../../src/components/DateRangePicker/DateRangePicker.tsx"],"sourcesContent":["import { useCallback, useMemo, useState } from \"react\";\nimport type { HTMLAttributes } from \"react\";\nimport { cn } from \"@/utils/cn\";\nimport styles from \"./DateRangePicker.module.css\";\n\nexport type WeekStart = 0 | 1;\n\n/** A selected range. Either bound may be `null` while picking. */\nexport interface DateRange {\n start: Date | null;\n end: Date | null;\n}\n\nexport interface DateRangePickerProps extends Omit<\n HTMLAttributes<HTMLDivElement>,\n \"onChange\" | \"defaultValue\"\n> {\n /** Controlled selected range. */\n value: DateRange;\n /** Called with the new range as the user picks start then end. */\n onChange: (range: DateRange) => void;\n /** How many month grids to show side by side. Default `2`. */\n numberOfMonths?: number;\n /** Initial visible month (uncontrolled). Defaults to the range start or today. */\n defaultMonth?: Date;\n minDate?: Date;\n maxDate?: Date;\n /** First column of the week — `0` Sunday (default) or `1` Monday. */\n weekStartsOn?: WeekStart;\n}\n\nconst WEEKDAY_LABELS = [\"Sun\", \"Mon\", \"Tue\", \"Wed\", \"Thu\", \"Fri\", \"Sat\"];\nconst MONTH_LABELS = [\n \"January\",\n \"February\",\n \"March\",\n \"April\",\n \"May\",\n \"June\",\n \"July\",\n \"August\",\n \"September\",\n \"October\",\n \"November\",\n \"December\",\n];\nconst DAYS_IN_GRID = 42;\n\nfunction makeDate(year: number, month: number, day: number): Date {\n return new Date(year, month, day);\n}\n\nfunction startOfDay(date: Date): Date {\n return new Date(date.getFullYear(), date.getMonth(), date.getDate());\n}\n\nfunction addMonths(date: Date, offset: number): Date {\n return makeDate(date.getFullYear(), date.getMonth() + offset, 1);\n}\n\nfunction isSameDay(a: Date, b: Date): boolean {\n return (\n a.getFullYear() === b.getFullYear() &&\n a.getMonth() === b.getMonth() &&\n a.getDate() === b.getDate()\n );\n}\n\nfunction isOutOfRange(date: Date, minDate?: Date, maxDate?: Date): boolean {\n const day = startOfDay(date).getTime();\n if (minDate && day < startOfDay(minDate).getTime()) return true;\n if (maxDate && day > startOfDay(maxDate).getTime()) return true;\n return false;\n}\n\n/** Inclusive-exclusive-agnostic \"strictly between a and b\" (a/b order-independent). */\nfunction isBetween(date: Date, a: Date, b: Date): boolean {\n const t = startOfDay(date).getTime();\n const lo = Math.min(startOfDay(a).getTime(), startOfDay(b).getTime());\n const hi = Math.max(startOfDay(a).getTime(), startOfDay(b).getTime());\n return t > lo && t < hi;\n}\n\nfunction buildGrid(month: Date, weekStartsOn: WeekStart): Date[] {\n const year = month.getFullYear();\n const monthIndex = month.getMonth();\n const firstWeekday = makeDate(year, monthIndex, 1).getDay();\n const leading = (firstWeekday - weekStartsOn + 7) % 7;\n const start = makeDate(year, monthIndex, 1 - leading);\n return Array.from({ length: DAYS_IN_GRID }, (_, i) =>\n makeDate(start.getFullYear(), start.getMonth(), start.getDate() + i),\n );\n}\n\n/**\n * Date-range picker: pick a start day, then an end day, across one or more\n * month grids. The hovered day previews the range before the end is committed.\n * Pure `Date` math — no date libraries. For a single date, use `Calendar`.\n *\n * Selection: first click sets `start` (clears `end`); the next click sets `end`\n * (auto-ordered if you click earlier than the start); a third click starts over.\n */\nexport function DateRangePicker({\n value,\n onChange,\n numberOfMonths = 2,\n defaultMonth,\n minDate,\n maxDate,\n weekStartsOn = 0,\n className,\n ...props\n}: DateRangePickerProps) {\n const [visibleMonth, setVisibleMonth] = useState<Date>(() => {\n const base = defaultMonth ?? value.start ?? new Date();\n return makeDate(base.getFullYear(), base.getMonth(), 1);\n });\n const [hovered, setHovered] = useState<Date | null>(null);\n\n const today = useMemo(() => startOfDay(new Date()), []);\n const weekdayLabels = useMemo(\n () => Array.from({ length: 7 }, (_, i) => WEEKDAY_LABELS[(i + weekStartsOn) % 7]),\n [weekStartsOn],\n );\n\n const selectDate = useCallback(\n (date: Date): void => {\n if (isOutOfRange(date, minDate, maxDate)) return;\n const { start, end } = value;\n if (!start || end) {\n onChange({ start: date, end: null });\n } else if (startOfDay(date).getTime() < startOfDay(start).getTime()) {\n onChange({ start: date, end: start });\n } else {\n onChange({ start, end: date });\n }\n },\n [value, onChange, minDate, maxDate],\n );\n\n const changeMonth = useCallback((offset: number): void => {\n setVisibleMonth((current) => addMonths(current, offset));\n }, []);\n\n // Effective end used for preview highlighting (hovered day while picking).\n const previewEnd = value.start && !value.end ? hovered : value.end;\n\n function dayState(date: Date): {\n selected: boolean;\n inRange: boolean;\n isStart: boolean;\n isEnd: boolean;\n } {\n const { start } = value;\n const end = previewEnd;\n const isStart = start ? isSameDay(date, start) : false;\n const isEnd = end ? isSameDay(date, end) : false;\n const inRange = start && end ? isBetween(date, start, end) : false;\n return { selected: isStart || isEnd, inRange, isStart, isEnd };\n }\n\n return (\n <div className={cn(styles.root, className)} {...props}>\n <div className={styles.header}>\n <button\n type=\"button\"\n className={styles.nav}\n aria-label=\"Previous month\"\n onClick={() => changeMonth(-1)}\n >\n {\"‹\"}\n </button>\n <span className={styles.headerSpacer} aria-hidden />\n <button\n type=\"button\"\n className={styles.nav}\n aria-label=\"Next month\"\n onClick={() => changeMonth(1)}\n >\n {\"›\"}\n </button>\n </div>\n\n <div className={styles.months} onMouseLeave={() => setHovered(null)}>\n {Array.from({ length: Math.max(1, numberOfMonths) }, (_, monthOffset) => {\n const month = addMonths(visibleMonth, monthOffset);\n const cells = buildGrid(month, weekStartsOn);\n return (\n <div key={monthOffset} className={styles.month}>\n <div className={styles.title} aria-live=\"polite\">\n {MONTH_LABELS[month.getMonth()]} {month.getFullYear()}\n </div>\n <div className={styles.weekdays} role=\"row\">\n {weekdayLabels.map((label) => (\n <span\n key={label}\n className={styles.weekday}\n role=\"columnheader\"\n >\n {label}\n </span>\n ))}\n </div>\n <div className={styles.grid} role=\"grid\">\n {cells.map((date, index) => {\n const inMonth = date.getMonth() === month.getMonth();\n const disabled = isOutOfRange(date, minDate, maxDate);\n const { selected, inRange, isStart, isEnd } = dayState(date);\n const isToday = isSameDay(date, today);\n return (\n <button\n key={index}\n type=\"button\"\n role=\"gridcell\"\n className={cn(\n styles.day,\n !inMonth && styles.outside,\n inRange && styles.inRange,\n selected && styles.selected,\n isStart && styles.rangeStart,\n isEnd && styles.rangeEnd,\n isToday && styles.today,\n )}\n aria-label={`${MONTH_LABELS[date.getMonth()]} ${date.getDate()}, ${date.getFullYear()}`}\n aria-selected={selected}\n aria-current={isToday ? \"date\" : undefined}\n disabled={disabled}\n onClick={() => selectDate(date)}\n onMouseEnter={() => setHovered(date)}\n >\n {date.getDate()}\n </button>\n );\n })}\n </div>\n </div>\n );\n })}\n </div>\n </div>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"DateRangePicker.js","names":[],"sources":["../../../src/components/DateRangePicker/DateRangePicker.tsx"],"sourcesContent":["/**\n * @tempest-limits file-lines, function-lines — a range is two dates whose validity\n * depends on each other, so the body keeps both ends, the hovered preview end and\n * the open panel in one place; splitting the halves would mean each one reaching\n * into the other's state to clamp itself.\n */\nimport { useCallback, useMemo, useState } from \"react\";\nimport type { HTMLAttributes } from \"react\";\nimport { cn } from \"@/utils/cn\";\nimport styles from \"./DateRangePicker.module.css\";\n\nexport type WeekStart = 0 | 1;\n\n/** A selected range. Either bound may be `null` while picking. */\nexport interface DateRange {\n start: Date | null;\n end: Date | null;\n}\n\nexport interface DateRangePickerProps extends Omit<\n HTMLAttributes<HTMLDivElement>,\n \"onChange\" | \"defaultValue\"\n> {\n /** Controlled selected range. */\n value: DateRange;\n /** Called with the new range as the user picks start then end. */\n onChange: (range: DateRange) => void;\n /** How many month grids to show side by side. Default `2`. */\n numberOfMonths?: number;\n /** Initial visible month (uncontrolled). Defaults to the range start or today. */\n defaultMonth?: Date;\n minDate?: Date;\n maxDate?: Date;\n /** First column of the week — `0` Sunday (default) or `1` Monday. */\n weekStartsOn?: WeekStart;\n}\n\nconst WEEKDAY_LABELS = [\"Sun\", \"Mon\", \"Tue\", \"Wed\", \"Thu\", \"Fri\", \"Sat\"];\nconst MONTH_LABELS = [\n \"January\",\n \"February\",\n \"March\",\n \"April\",\n \"May\",\n \"June\",\n \"July\",\n \"August\",\n \"September\",\n \"October\",\n \"November\",\n \"December\",\n];\nconst DAYS_IN_GRID = 42;\n\nfunction makeDate(year: number, month: number, day: number): Date {\n return new Date(year, month, day);\n}\n\nfunction startOfDay(date: Date): Date {\n return new Date(date.getFullYear(), date.getMonth(), date.getDate());\n}\n\nfunction addMonths(date: Date, offset: number): Date {\n return makeDate(date.getFullYear(), date.getMonth() + offset, 1);\n}\n\nfunction isSameDay(a: Date, b: Date): boolean {\n return (\n a.getFullYear() === b.getFullYear() &&\n a.getMonth() === b.getMonth() &&\n a.getDate() === b.getDate()\n );\n}\n\nfunction isOutOfRange(date: Date, minDate?: Date, maxDate?: Date): boolean {\n const day = startOfDay(date).getTime();\n if (minDate && day < startOfDay(minDate).getTime()) return true;\n if (maxDate && day > startOfDay(maxDate).getTime()) return true;\n return false;\n}\n\n/** Inclusive-exclusive-agnostic \"strictly between a and b\" (a/b order-independent). */\nfunction isBetween(date: Date, a: Date, b: Date): boolean {\n const t = startOfDay(date).getTime();\n const lo = Math.min(startOfDay(a).getTime(), startOfDay(b).getTime());\n const hi = Math.max(startOfDay(a).getTime(), startOfDay(b).getTime());\n return t > lo && t < hi;\n}\n\nfunction buildGrid(month: Date, weekStartsOn: WeekStart): Date[] {\n const year = month.getFullYear();\n const monthIndex = month.getMonth();\n const firstWeekday = makeDate(year, monthIndex, 1).getDay();\n const leading = (firstWeekday - weekStartsOn + 7) % 7;\n const start = makeDate(year, monthIndex, 1 - leading);\n return Array.from({ length: DAYS_IN_GRID }, (_, i) =>\n makeDate(start.getFullYear(), start.getMonth(), start.getDate() + i),\n );\n}\n\n/**\n * Date-range picker: pick a start day, then an end day, across one or more\n * month grids. The hovered day previews the range before the end is committed.\n * Pure `Date` math — no date libraries. For a single date, use `Calendar`.\n *\n * Selection: first click sets `start` (clears `end`); the next click sets `end`\n * (auto-ordered if you click earlier than the start); a third click starts over.\n */\nexport function DateRangePicker({\n value,\n onChange,\n numberOfMonths = 2,\n defaultMonth,\n minDate,\n maxDate,\n weekStartsOn = 0,\n className,\n ...props\n}: DateRangePickerProps) {\n const [visibleMonth, setVisibleMonth] = useState<Date>(() => {\n const base = defaultMonth ?? value.start ?? new Date();\n return makeDate(base.getFullYear(), base.getMonth(), 1);\n });\n const [hovered, setHovered] = useState<Date | null>(null);\n\n const today = useMemo(() => startOfDay(new Date()), []);\n const weekdayLabels = useMemo(\n () => Array.from({ length: 7 }, (_, i) => WEEKDAY_LABELS[(i + weekStartsOn) % 7]),\n [weekStartsOn],\n );\n\n const selectDate = useCallback(\n (date: Date): void => {\n if (isOutOfRange(date, minDate, maxDate)) return;\n const { start, end } = value;\n if (!start || end) {\n onChange({ start: date, end: null });\n } else if (startOfDay(date).getTime() < startOfDay(start).getTime()) {\n onChange({ start: date, end: start });\n } else {\n onChange({ start, end: date });\n }\n },\n [value, onChange, minDate, maxDate],\n );\n\n const changeMonth = useCallback((offset: number): void => {\n setVisibleMonth((current) => addMonths(current, offset));\n }, []);\n\n // Effective end used for preview highlighting (hovered day while picking).\n const previewEnd = value.start && !value.end ? hovered : value.end;\n\n function dayState(date: Date): {\n selected: boolean;\n inRange: boolean;\n isStart: boolean;\n isEnd: boolean;\n } {\n const { start } = value;\n const end = previewEnd;\n const isStart = start ? isSameDay(date, start) : false;\n const isEnd = end ? isSameDay(date, end) : false;\n const inRange = start && end ? isBetween(date, start, end) : false;\n return { selected: isStart || isEnd, inRange, isStart, isEnd };\n }\n\n return (\n <div className={cn(styles.root, className)} {...props}>\n <div className={styles.header}>\n <button\n type=\"button\"\n className={styles.nav}\n aria-label=\"Previous month\"\n onClick={() => changeMonth(-1)}\n >\n {\"‹\"}\n </button>\n <span className={styles.headerSpacer} aria-hidden />\n <button\n type=\"button\"\n className={styles.nav}\n aria-label=\"Next month\"\n onClick={() => changeMonth(1)}\n >\n {\"›\"}\n </button>\n </div>\n\n <div className={styles.months} onMouseLeave={() => setHovered(null)}>\n {Array.from({ length: Math.max(1, numberOfMonths) }, (_, monthOffset) => {\n const month = addMonths(visibleMonth, monthOffset);\n const cells = buildGrid(month, weekStartsOn);\n return (\n <div key={monthOffset} className={styles.month}>\n <div className={styles.title} aria-live=\"polite\">\n {MONTH_LABELS[month.getMonth()]} {month.getFullYear()}\n </div>\n <div className={styles.weekdays} role=\"row\">\n {weekdayLabels.map((label) => (\n <span\n key={label}\n className={styles.weekday}\n role=\"columnheader\"\n >\n {label}\n </span>\n ))}\n </div>\n <div className={styles.grid} role=\"grid\">\n {cells.map((date, index) => {\n const inMonth = date.getMonth() === month.getMonth();\n const disabled = isOutOfRange(date, minDate, maxDate);\n const { selected, inRange, isStart, isEnd } = dayState(date);\n const isToday = isSameDay(date, today);\n return (\n <button\n key={index}\n type=\"button\"\n role=\"gridcell\"\n className={cn(\n styles.day,\n !inMonth && styles.outside,\n inRange && styles.inRange,\n selected && styles.selected,\n isStart && styles.rangeStart,\n isEnd && styles.rangeEnd,\n isToday && styles.today,\n )}\n aria-label={`${MONTH_LABELS[date.getMonth()]} ${date.getDate()}, ${date.getFullYear()}`}\n aria-selected={selected}\n aria-current={isToday ? \"date\" : undefined}\n disabled={disabled}\n onClick={() => selectDate(date)}\n onMouseEnter={() => setHovered(date)}\n >\n {date.getDate()}\n </button>\n );\n })}\n </div>\n </div>\n );\n })}\n </div>\n </div>\n );\n}\n"],"mappings":";;;;;AAqCA,IAAM,IAAiB;CAAC;CAAO;CAAO;CAAO;CAAO;CAAO;CAAO;AAAK,GACjE,IAAe;CACjB;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AACJ,GACM,IAAe;AAErB,SAAS,EAAS,GAAc,GAAe,GAAmB;CAC9D,OAAO,IAAI,KAAK,GAAM,GAAO,CAAG;AACpC;AAEA,SAAS,EAAW,GAAkB;CAClC,OAAO,IAAI,KAAK,EAAK,YAAY,GAAG,EAAK,SAAS,GAAG,EAAK,QAAQ,CAAC;AACvE;AAEA,SAAS,EAAU,GAAY,GAAsB;CACjD,OAAO,EAAS,EAAK,YAAY,GAAG,EAAK,SAAS,IAAI,GAAQ,CAAC;AACnE;AAEA,SAAS,EAAU,GAAS,GAAkB;CAC1C,OACI,EAAE,YAAY,MAAM,EAAE,YAAY,KAClC,EAAE,SAAS,MAAM,EAAE,SAAS,KAC5B,EAAE,QAAQ,MAAM,EAAE,QAAQ;AAElC;AAEA,SAAS,EAAa,GAAY,GAAgB,GAAyB;CACvE,IAAM,IAAM,EAAW,CAAI,CAAC,CAAC,QAAQ;CAGrC,OADA,GADI,KAAW,IAAM,EAAW,CAAO,CAAC,CAAC,QAAQ,KAC7C,KAAW,IAAM,EAAW,CAAO,CAAC,CAAC,QAAQ;AAErD;AAGA,SAAS,EAAU,GAAY,GAAS,GAAkB;CACtD,IAAM,IAAI,EAAW,CAAI,CAAC,CAAC,QAAQ,GAC7B,IAAK,KAAK,IAAI,EAAW,CAAC,CAAC,CAAC,QAAQ,GAAG,EAAW,CAAC,CAAC,CAAC,QAAQ,CAAC,GAC9D,IAAK,KAAK,IAAI,EAAW,CAAC,CAAC,CAAC,QAAQ,GAAG,EAAW,CAAC,CAAC,CAAC,QAAQ,CAAC;CACpE,OAAO,IAAI,KAAM,IAAI;AACzB;AAEA,SAAS,EAAU,GAAa,GAAiC;CAC7D,IAAM,IAAO,EAAM,YAAY,GACzB,IAAa,EAAM,SAAS,GAG5B,IAAQ,EAAS,GAAM,GAAY,KAFpB,EAAS,GAAM,GAAY,CAAC,CAAC,CAAC,OAClC,IAAe,IAAe,KAAK,CACA;CACpD,OAAO,MAAM,KAAK,EAAE,QAAQ,EAAa,IAAI,GAAG,MAC5C,EAAS,EAAM,YAAY,GAAG,EAAM,SAAS,GAAG,EAAM,QAAQ,IAAI,CAAC,CACvE;AACJ;AAUA,SAAgB,EAAgB,EAC5B,UACA,aACA,oBAAiB,GACjB,iBACA,YACA,YACA,kBAAe,GACf,cACA,GAAG,KACkB;CACrB,IAAM,CAAC,GAAc,KAAmB,QAAqB;EACzD,IAAM,IAAO,KAAgB,EAAM,yBAAS,IAAI,KAAK;EACrD,OAAO,EAAS,EAAK,YAAY,GAAG,EAAK,SAAS,GAAG,CAAC;CAC1D,CAAC,GACK,CAAC,GAAS,KAAc,EAAsB,IAAI,GAElD,IAAQ,QAAc,kBAAW,IAAI,KAAK,CAAC,GAAG,CAAC,CAAC,GAChD,IAAgB,QACZ,MAAM,KAAK,EAAE,QAAQ,EAAE,IAAI,GAAG,MAAM,GAAgB,IAAI,KAAgB,EAAE,GAChF,CAAC,CAAY,CACjB,GAEM,IAAa,GACd,MAAqB;EAClB,IAAI,EAAa,GAAM,GAAS,CAAO,GAAG;EAC1C,IAAM,EAAE,UAAO,WAAQ;EACvB,AAAI,CAAC,KAAS,IACV,EAAS;GAAE,OAAO;GAAM,KAAK;EAAK,CAAC,IAC5B,EAAW,CAAI,CAAC,CAAC,QAAQ,IAAI,EAAW,CAAK,CAAC,CAAC,QAAQ,IAC9D,EAAS;GAAE,OAAO;GAAM,KAAK;EAAM,CAAC,IAEpC,EAAS;GAAE;GAAO,KAAK;EAAK,CAAC;CAErC,GACA;EAAC;EAAO;EAAU;EAAS;CAAO,CACtC,GAEM,IAAc,GAAa,MAAyB;EACtD,GAAiB,MAAY,EAAU,GAAS,CAAM,CAAC;CAC3D,GAAG,CAAC,CAAC,GAGC,IAAa,EAAM,SAAS,CAAC,EAAM,MAAM,IAAU,EAAM;CAE/D,SAAS,EAAS,GAKhB;EACE,IAAM,EAAE,aAAU,GACZ,IAAM,GACN,IAAU,IAAQ,EAAU,GAAM,CAAK,IAAI,IAC3C,IAAQ,IAAM,EAAU,GAAM,CAAG,IAAI,IACrC,IAAU,KAAS,IAAM,EAAU,GAAM,GAAO,CAAG,IAAI;EAC7D,OAAO;GAAE,UAAU,KAAW;GAAO;GAAS;GAAS;EAAM;CACjE;CAEA,OACI,kBAAC,OAAD;EAAK,WAAW,EAAG,EAAO,MAAM,CAAS;EAAG,GAAI;YAAhD,CACI,kBAAC,OAAD;GAAK,WAAW,EAAO;aAAvB;IACI,kBAAC,UAAD;KACI,MAAK;KACL,WAAW,EAAO;KAClB,cAAW;KACX,eAAe,EAAY,EAAE;eAE5B;IACG,CAAA;IACR,kBAAC,QAAD;KAAM,WAAW,EAAO;KAAc,eAAA;IAAa,CAAA;IACnD,kBAAC,UAAD;KACI,MAAK;KACL,WAAW,EAAO;KAClB,cAAW;KACX,eAAe,EAAY,CAAC;eAE3B;IACG,CAAA;GACP;MAEL,kBAAC,OAAD;GAAK,WAAW,EAAO;GAAQ,oBAAoB,EAAW,IAAI;aAC7D,MAAM,KAAK,EAAE,QAAQ,KAAK,IAAI,GAAG,CAAc,EAAE,IAAI,GAAG,MAAgB;IACrE,IAAM,IAAQ,EAAU,GAAc,CAAW,GAC3C,IAAQ,EAAU,GAAO,CAAY;IAC3C,OACI,kBAAC,OAAD;KAAuB,WAAW,EAAO;eAAzC;MACI,kBAAC,OAAD;OAAK,WAAW,EAAO;OAAO,aAAU;iBAAxC;QACK,EAAa,EAAM,SAAS;QAAG;QAAE,EAAM,YAAY;OACnD;;MACL,kBAAC,OAAD;OAAK,WAAW,EAAO;OAAU,MAAK;iBACjC,EAAc,KAAK,MAChB,kBAAC,QAAD;QAEI,WAAW,EAAO;QAClB,MAAK;kBAEJ;OACC,GALG,CAKH,CACT;MACA,CAAA;MACL,kBAAC,OAAD;OAAK,WAAW,EAAO;OAAM,MAAK;iBAC7B,EAAM,KAAK,GAAM,MAAU;QACxB,IAAM,IAAU,EAAK,SAAS,MAAM,EAAM,SAAS,GAC7C,IAAW,EAAa,GAAM,GAAS,CAAO,GAC9C,EAAE,aAAU,YAAS,YAAS,aAAU,EAAS,CAAI,GACrD,IAAU,EAAU,GAAM,CAAK;QACrC,OACI,kBAAC,UAAD;SAEI,MAAK;SACL,MAAK;SACL,WAAW,EACP,EAAO,KACP,CAAC,KAAW,EAAO,SACnB,KAAW,EAAO,SAClB,KAAY,EAAO,UACnB,KAAW,EAAO,YAClB,KAAS,EAAO,UAChB,KAAW,EAAO,KACtB;SACA,cAAY,GAAG,EAAa,EAAK,SAAS,GAAG,GAAG,EAAK,QAAQ,EAAE,IAAI,EAAK,YAAY;SACpF,iBAAe;SACf,gBAAc,IAAU,SAAS,KAAA;SACvB;SACV,eAAe,EAAW,CAAI;SAC9B,oBAAoB,EAAW,CAAI;mBAElC,EAAK,QAAQ;QACV,GApBC,CAoBD;OAEhB,CAAC;MACA,CAAA;KACJ;OA/CK,CA+CL;GAEb,CAAC;EACA,CAAA,CACJ;;AAEb"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Drawer.cjs","names":[],"sources":["../../../src/components/Drawer/Drawer.tsx"],"sourcesContent":["
|
|
1
|
+
{"version":3,"file":"Drawer.cjs","names":[],"sources":["../../../src/components/Drawer/Drawer.tsx"],"sourcesContent":["/**\n * @tempest-limits props-count, function-lines — placement plus mobilePlacement is\n * the whole point — a drawer that comes from the side on desktop and from the bottom\n * on a phone — and the rest is the dialog contract the SDK's Modal also has (open,\n * onClose, title, children, footer, closeOnBackdrop, closeOnEsc, hideCloseButton)\n * plus showHandle for the sheet affordance.\n */\nimport { useEffect } from \"react\";\nimport type { ReactNode } from \"react\";\nimport { createPortal } from \"react-dom\";\nimport { cn } from \"@/utils/cn\";\nimport { useBreakpoint } from \"@/hooks/use-breakpoint\";\nimport styles from \"./Drawer.module.css\";\n\nexport type DrawerPlacement = \"right\" | \"left\" | \"top\" | \"bottom\";\n\nexport interface DrawerProps {\n open: boolean;\n onClose: () => void;\n placement?: DrawerPlacement;\n title?: ReactNode;\n children?: ReactNode;\n footer?: ReactNode;\n closeOnBackdrop?: boolean;\n closeOnEsc?: boolean;\n hideCloseButton?: boolean;\n className?: string;\n /**\n * Auto-switch to bottom-sheet placement on mobile viewports (< md).\n * Modern mobile apps default to bottom drawers; on desktop the original\n * `placement` is preserved.\n */\n mobilePlacement?: DrawerPlacement;\n /** Render a drag handle indicator at the leading edge (bottom-sheet style). */\n showHandle?: boolean;\n}\n\n/**\n * Sliding side panel. Same building blocks as {@link Modal} but anchored to\n * an edge. Locks body scroll while open.\n */\nexport function Drawer({\n open,\n onClose,\n placement = \"right\",\n title,\n children,\n footer,\n closeOnBackdrop = true,\n closeOnEsc = true,\n hideCloseButton = false,\n className,\n mobilePlacement,\n showHandle = false,\n}: DrawerProps) {\n const { isMobile } = useBreakpoint();\n const effectivePlacement: DrawerPlacement =\n isMobile && mobilePlacement ? mobilePlacement : placement;\n\n useEffect(() => {\n if (!open) return;\n const previousOverflow = document.body.style.overflow;\n document.body.style.overflow = \"hidden\";\n const handleKey = (event: KeyboardEvent): void => {\n if (closeOnEsc && event.key === \"Escape\") onClose();\n };\n window.addEventListener(\"keydown\", handleKey);\n return () => {\n document.body.style.overflow = previousOverflow;\n window.removeEventListener(\"keydown\", handleKey);\n };\n }, [open, closeOnEsc, onClose]);\n\n if (!open || typeof document === \"undefined\") return null;\n\n return createPortal(\n <>\n <div\n className={styles.overlay}\n onClick={() => {\n if (closeOnBackdrop) onClose();\n }}\n />\n <aside\n role=\"dialog\"\n aria-modal=\"true\"\n className={cn(styles.panel, styles[effectivePlacement], className)}\n >\n {showHandle &&\n (effectivePlacement === \"bottom\" || effectivePlacement === \"top\") && (\n <div\n className={cn(\n styles.handle,\n effectivePlacement === \"top\" && styles.handleTop,\n )}\n aria-hidden\n />\n )}\n {(title || !hideCloseButton) && (\n <header className={styles.header}>\n <h3 className={styles.title}>{title}</h3>\n {!hideCloseButton && (\n <button\n type=\"button\"\n aria-label=\"Fechar\"\n className={styles.close}\n onClick={onClose}\n >\n <CloseIcon />\n </button>\n )}\n </header>\n )}\n <div className={styles.body}>{children}</div>\n {footer && <footer className={styles.footer}>{footer}</footer>}\n </aside>\n </>,\n document.body,\n );\n}\n\nfunction CloseIcon() {\n return (\n <svg width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\">\n <path\n d=\"M6 6l12 12M6 18L18 6\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n />\n </svg>\n );\n}\n"],"mappings":"gMAyCA,SAAgB,EAAO,CACnB,OACA,UACA,YAAY,QACZ,QACA,WACA,SACA,kBAAkB,GAClB,aAAa,GACb,kBAAkB,GAClB,YACA,kBACA,aAAa,IACD,CACZ,GAAM,CAAE,YAAa,EAAA,cAAc,EAC7B,EACF,GAAY,EAAkB,EAAkB,EAkBpD,OAhBA,EAAA,EAAA,UAAA,KAAgB,CACZ,GAAI,CAAC,EAAM,OACX,IAAM,EAAmB,SAAS,KAAK,MAAM,SAC7C,SAAS,KAAK,MAAM,SAAW,SAC/B,IAAM,EAAa,GAA+B,CAC1C,GAAc,EAAM,MAAQ,UAAU,EAAQ,CACtD,EAEA,OADA,OAAO,iBAAiB,UAAW,CAAS,MAC/B,CACT,SAAS,KAAK,MAAM,SAAW,EAC/B,OAAO,oBAAoB,UAAW,CAAS,CACnD,CACJ,EAAG,CAAC,EAAM,EAAY,CAAO,CAAC,EAE1B,CAAC,GAAQ,OAAO,SAAa,IAAoB,MAErD,EAAA,EAAA,aAAA,EACI,EAAA,EAAA,KAAA,CAAA,EAAA,SAAA,CAAA,SAAA,EACI,EAAA,EAAA,IAAA,CAAC,MAAD,CACI,UAAW,EAAA,QAAO,QAClB,YAAe,CACP,GAAiB,EAAQ,CACjC,CACH,CAAA,GACD,EAAA,EAAA,KAAA,CAAC,QAAD,CACI,KAAK,SACL,aAAW,OACX,UAAW,EAAA,GAAG,EAAA,QAAO,MAAO,EAAA,QAAO,GAAqB,CAAS,WAHrE,CAKK,IACI,IAAuB,UAAY,IAAuB,SACvD,EAAA,EAAA,IAAA,CAAC,MAAD,CACI,UAAW,EAAA,GACP,EAAA,QAAO,OACP,IAAuB,OAAS,EAAA,QAAO,SAC3C,EACA,cAAA,EACH,CAAA,GAEP,GAAS,CAAC,KACR,EAAA,EAAA,KAAA,CAAC,SAAD,CAAQ,UAAW,EAAA,QAAO,gBAA1B,EACI,EAAA,EAAA,IAAA,CAAC,KAAD,CAAI,UAAW,EAAA,QAAO,eAAQ,CAAU,CAAA,EACvC,CAAC,IACE,EAAA,EAAA,IAAA,CAAC,SAAD,CACI,KAAK,SACL,aAAW,SACX,UAAW,EAAA,QAAO,MAClB,QAAS,YAET,EAAA,EAAA,IAAA,CAAC,EAAD,CAAY,CAAA,CACR,CAAA,CAER,KAEZ,EAAA,EAAA,IAAA,CAAC,MAAD,CAAK,UAAW,EAAA,QAAO,KAAO,UAAc,CAAA,EAC3C,IAAU,EAAA,EAAA,IAAA,CAAC,SAAD,CAAQ,UAAW,EAAA,QAAO,gBAAS,CAAe,CAAA,CAC1D,GACT,CAAA,CAAA,EACF,SAAS,IACb,CACJ,CAEA,SAAS,GAAY,CACjB,OACI,EAAA,EAAA,IAAA,CAAC,MAAD,CAAK,MAAM,KAAK,OAAO,KAAK,QAAQ,YAAY,KAAK,iBACjD,EAAA,EAAA,IAAA,CAAC,OAAD,CACI,EAAE,uBACF,OAAO,eACP,YAAY,IACZ,cAAc,OACjB,CAAA,CACA,CAAA,CAEb"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Drawer.js","names":[],"sources":["../../../src/components/Drawer/Drawer.tsx"],"sourcesContent":["
|
|
1
|
+
{"version":3,"file":"Drawer.js","names":[],"sources":["../../../src/components/Drawer/Drawer.tsx"],"sourcesContent":["/**\n * @tempest-limits props-count, function-lines — placement plus mobilePlacement is\n * the whole point — a drawer that comes from the side on desktop and from the bottom\n * on a phone — and the rest is the dialog contract the SDK's Modal also has (open,\n * onClose, title, children, footer, closeOnBackdrop, closeOnEsc, hideCloseButton)\n * plus showHandle for the sheet affordance.\n */\nimport { useEffect } from \"react\";\nimport type { ReactNode } from \"react\";\nimport { createPortal } from \"react-dom\";\nimport { cn } from \"@/utils/cn\";\nimport { useBreakpoint } from \"@/hooks/use-breakpoint\";\nimport styles from \"./Drawer.module.css\";\n\nexport type DrawerPlacement = \"right\" | \"left\" | \"top\" | \"bottom\";\n\nexport interface DrawerProps {\n open: boolean;\n onClose: () => void;\n placement?: DrawerPlacement;\n title?: ReactNode;\n children?: ReactNode;\n footer?: ReactNode;\n closeOnBackdrop?: boolean;\n closeOnEsc?: boolean;\n hideCloseButton?: boolean;\n className?: string;\n /**\n * Auto-switch to bottom-sheet placement on mobile viewports (< md).\n * Modern mobile apps default to bottom drawers; on desktop the original\n * `placement` is preserved.\n */\n mobilePlacement?: DrawerPlacement;\n /** Render a drag handle indicator at the leading edge (bottom-sheet style). */\n showHandle?: boolean;\n}\n\n/**\n * Sliding side panel. Same building blocks as {@link Modal} but anchored to\n * an edge. Locks body scroll while open.\n */\nexport function Drawer({\n open,\n onClose,\n placement = \"right\",\n title,\n children,\n footer,\n closeOnBackdrop = true,\n closeOnEsc = true,\n hideCloseButton = false,\n className,\n mobilePlacement,\n showHandle = false,\n}: DrawerProps) {\n const { isMobile } = useBreakpoint();\n const effectivePlacement: DrawerPlacement =\n isMobile && mobilePlacement ? mobilePlacement : placement;\n\n useEffect(() => {\n if (!open) return;\n const previousOverflow = document.body.style.overflow;\n document.body.style.overflow = \"hidden\";\n const handleKey = (event: KeyboardEvent): void => {\n if (closeOnEsc && event.key === \"Escape\") onClose();\n };\n window.addEventListener(\"keydown\", handleKey);\n return () => {\n document.body.style.overflow = previousOverflow;\n window.removeEventListener(\"keydown\", handleKey);\n };\n }, [open, closeOnEsc, onClose]);\n\n if (!open || typeof document === \"undefined\") return null;\n\n return createPortal(\n <>\n <div\n className={styles.overlay}\n onClick={() => {\n if (closeOnBackdrop) onClose();\n }}\n />\n <aside\n role=\"dialog\"\n aria-modal=\"true\"\n className={cn(styles.panel, styles[effectivePlacement], className)}\n >\n {showHandle &&\n (effectivePlacement === \"bottom\" || effectivePlacement === \"top\") && (\n <div\n className={cn(\n styles.handle,\n effectivePlacement === \"top\" && styles.handleTop,\n )}\n aria-hidden\n />\n )}\n {(title || !hideCloseButton) && (\n <header className={styles.header}>\n <h3 className={styles.title}>{title}</h3>\n {!hideCloseButton && (\n <button\n type=\"button\"\n aria-label=\"Fechar\"\n className={styles.close}\n onClick={onClose}\n >\n <CloseIcon />\n </button>\n )}\n </header>\n )}\n <div className={styles.body}>{children}</div>\n {footer && <footer className={styles.footer}>{footer}</footer>}\n </aside>\n </>,\n document.body,\n );\n}\n\nfunction CloseIcon() {\n return (\n <svg width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\">\n <path\n d=\"M6 6l12 12M6 18L18 6\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n />\n </svg>\n );\n}\n"],"mappings":";;;;;;;AAyCA,SAAgB,EAAO,EACnB,SACA,YACA,eAAY,SACZ,UACA,aACA,WACA,qBAAkB,IAClB,gBAAa,IACb,qBAAkB,IAClB,cACA,oBACA,gBAAa,MACD;CACZ,IAAM,EAAE,gBAAa,EAAc,GAC7B,IACF,KAAY,IAAkB,IAAkB;CAkBpD,OAhBA,QAAgB;EACZ,IAAI,CAAC,GAAM;EACX,IAAM,IAAmB,SAAS,KAAK,MAAM;EAC7C,SAAS,KAAK,MAAM,WAAW;EAC/B,IAAM,KAAa,MAA+B;GAC9C,AAAI,KAAc,EAAM,QAAQ,YAAU,EAAQ;EACtD;EAEA,OADA,OAAO,iBAAiB,WAAW,CAAS,SAC/B;GAET,AADA,SAAS,KAAK,MAAM,WAAW,GAC/B,OAAO,oBAAoB,WAAW,CAAS;EACnD;CACJ,GAAG;EAAC;EAAM;EAAY;CAAO,CAAC,GAE1B,CAAC,KAAQ,OAAO,WAAa,MAAoB,OAE9C,EACH,kBAAA,GAAA,EAAA,UAAA,CACI,kBAAC,OAAD;EACI,WAAW,EAAO;EAClB,eAAe;GACX,AAAI,KAAiB,EAAQ;EACjC;CACH,CAAA,GACD,kBAAC,SAAD;EACI,MAAK;EACL,cAAW;EACX,WAAW,EAAG,EAAO,OAAO,EAAO,IAAqB,CAAS;YAHrE;GAKK,MACI,MAAuB,YAAY,MAAuB,UACvD,kBAAC,OAAD;IACI,WAAW,EACP,EAAO,QACP,MAAuB,SAAS,EAAO,SAC3C;IACA,eAAA;GACH,CAAA;IAEP,KAAS,CAAC,MACR,kBAAC,UAAD;IAAQ,WAAW,EAAO;cAA1B,CACI,kBAAC,MAAD;KAAI,WAAW,EAAO;eAAQ;IAAU,CAAA,GACvC,CAAC,KACE,kBAAC,UAAD;KACI,MAAK;KACL,cAAW;KACX,WAAW,EAAO;KAClB,SAAS;eAET,kBAAC,GAAD,CAAY,CAAA;IACR,CAAA,CAER;;GAEZ,kBAAC,OAAD;IAAK,WAAW,EAAO;IAAO;GAAc,CAAA;GAC3C,KAAU,kBAAC,UAAD;IAAQ,WAAW,EAAO;cAAS;GAAe,CAAA;EAC1D;GACT,EAAA,CAAA,GACF,SAAS,IACb;AACJ;AAEA,SAAS,IAAY;CACjB,OACI,kBAAC,OAAD;EAAK,OAAM;EAAK,QAAO;EAAK,SAAQ;EAAY,MAAK;YACjD,kBAAC,QAAD;GACI,GAAE;GACF,QAAO;GACP,aAAY;GACZ,eAAc;EACjB,CAAA;CACA,CAAA;AAEb"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DropdownMenu.cjs","names":[],"sources":["../../../src/components/DropdownMenu/DropdownMenu.tsx"],"sourcesContent":["
|
|
1
|
+
{"version":3,"file":"DropdownMenu.cjs","names":[],"sources":["../../../src/components/DropdownMenu/DropdownMenu.tsx"],"sourcesContent":["/**\n * @tempest-limits file-lines, function-lines — the body owns placement, outside-\n * click, Escape, and roving focus over items that can be disabled or separators —\n * one keyboard model that has to see the whole item list to know where the next stop\n * is.\n */\nimport { useCallback, useEffect, useId, useRef, useState } from \"react\";\nimport type { ReactElement, ReactNode } from \"react\";\nimport { cn } from \"@/utils/cn\";\nimport styles from \"./DropdownMenu.module.css\";\n\nexport type DropdownMenuPlacement = \"bottom-start\" | \"bottom-end\" | \"top-start\" | \"top-end\";\n\nexport type DropdownMenuEntry =\n | {\n type: \"item\";\n id: string;\n label: ReactNode;\n icon?: ReactNode;\n danger?: boolean;\n disabled?: boolean;\n onSelect: () => void;\n }\n | { type: \"separator\"; id: string }\n | { type: \"label\"; id: string; label: ReactNode };\n\nexport interface DropdownMenuProps {\n trigger: ReactElement<{\n onClick?: (e: React.MouseEvent) => void;\n \"aria-expanded\"?: boolean;\n \"aria-controls\"?: string;\n \"aria-haspopup\"?: boolean | \"menu\";\n }>;\n items: DropdownMenuEntry[];\n placement?: DropdownMenuPlacement;\n className?: string;\n}\n\nfunction placementClass(placement: DropdownMenuPlacement): string {\n switch (placement) {\n case \"bottom-end\":\n return styles.bottomEnd;\n case \"top-start\":\n return styles.topStart;\n case \"top-end\":\n return styles.topEnd;\n case \"bottom-start\":\n default:\n return styles.bottomStart;\n }\n}\n\n/**\n * Dropdown menu — list of selectable actions anchored to a trigger.\n *\n * - Toggle on trigger click.\n * - Close on outside click / Escape / item selection.\n * - Arrow keys navigate, Enter activates focused item.\n */\nexport function DropdownMenu({\n trigger,\n items,\n placement = \"bottom-start\",\n className,\n}: DropdownMenuProps) {\n const [open, setOpen] = useState(false);\n const [activeIndex, setActiveIndex] = useState<number>(-1);\n const id = useId();\n const rootRef = useRef<HTMLSpanElement>(null);\n const itemRefs = useRef<Array<HTMLButtonElement | null>>([]);\n\n const selectableIndexes = items\n .map((entry, index) => (entry.type === \"item\" && !entry.disabled ? index : -1))\n .filter((i) => i !== -1);\n\n const close = useCallback((): void => {\n setOpen(false);\n setActiveIndex(-1);\n }, []);\n\n useEffect(() => {\n if (!open) return;\n const onKey = (event: KeyboardEvent): void => {\n if (event.key === \"Escape\") {\n close();\n return;\n }\n if (event.key === \"ArrowDown\") {\n event.preventDefault();\n const current = selectableIndexes.indexOf(activeIndex);\n const next = selectableIndexes[(current + 1) % selectableIndexes.length] ?? -1;\n setActiveIndex(next);\n itemRefs.current[next]?.focus();\n }\n if (event.key === \"ArrowUp\") {\n event.preventDefault();\n const current = selectableIndexes.indexOf(activeIndex);\n const prev =\n selectableIndexes[\n (current - 1 + selectableIndexes.length) % selectableIndexes.length\n ] ?? -1;\n setActiveIndex(prev);\n itemRefs.current[prev]?.focus();\n }\n };\n const onDown = (event: MouseEvent): void => {\n if (rootRef.current && !rootRef.current.contains(event.target as Node)) close();\n };\n window.addEventListener(\"keydown\", onKey);\n window.addEventListener(\"mousedown\", onDown);\n return () => {\n window.removeEventListener(\"keydown\", onKey);\n window.removeEventListener(\"mousedown\", onDown);\n };\n }, [open, activeIndex, selectableIndexes, close]);\n\n const handleTriggerClick = (event: React.MouseEvent): void => {\n trigger.props.onClick?.(event);\n setOpen((prev) => !prev);\n };\n\n const triggerClone = {\n ...trigger,\n props: {\n ...trigger.props,\n onClick: handleTriggerClick,\n \"aria-expanded\": open,\n \"aria-controls\": id,\n \"aria-haspopup\": \"menu\" as const,\n },\n } as ReactElement;\n\n const handleSelect = (entry: Extract<DropdownMenuEntry, { type: \"item\" }>): void => {\n entry.onSelect();\n close();\n };\n\n return (\n <span ref={rootRef} className={styles.root}>\n {triggerClone}\n {open && (\n <ul\n id={id}\n role=\"menu\"\n className={cn(styles.menu, placementClass(placement), className)}\n >\n {items.map((entry, index) => {\n if (entry.type === \"separator\") {\n return (\n <li\n key={entry.id}\n role=\"separator\"\n className={styles.separator}\n aria-hidden\n />\n );\n }\n if (entry.type === \"label\") {\n return (\n <li key={entry.id} role=\"presentation\" className={styles.label}>\n {entry.label}\n </li>\n );\n }\n return (\n <li key={entry.id} role=\"none\">\n <button\n ref={(el) => {\n itemRefs.current[index] = el;\n }}\n type=\"button\"\n role=\"menuitem\"\n className={cn(\n styles.item,\n entry.danger && styles.danger,\n activeIndex === index && styles.active,\n )}\n disabled={entry.disabled}\n onClick={() => handleSelect(entry)}\n onMouseEnter={() => setActiveIndex(index)}\n >\n {entry.icon && <span aria-hidden>{entry.icon}</span>}\n {entry.label}\n </button>\n </li>\n );\n })}\n </ul>\n )}\n </span>\n );\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DropdownMenu.js","names":[],"sources":["../../../src/components/DropdownMenu/DropdownMenu.tsx"],"sourcesContent":["
|
|
1
|
+
{"version":3,"file":"DropdownMenu.js","names":[],"sources":["../../../src/components/DropdownMenu/DropdownMenu.tsx"],"sourcesContent":["/**\n * @tempest-limits file-lines, function-lines — the body owns placement, outside-\n * click, Escape, and roving focus over items that can be disabled or separators —\n * one keyboard model that has to see the whole item list to know where the next stop\n * is.\n */\nimport { useCallback, useEffect, useId, useRef, useState } from \"react\";\nimport type { ReactElement, ReactNode } from \"react\";\nimport { cn } from \"@/utils/cn\";\nimport styles from \"./DropdownMenu.module.css\";\n\nexport type DropdownMenuPlacement = \"bottom-start\" | \"bottom-end\" | \"top-start\" | \"top-end\";\n\nexport type DropdownMenuEntry =\n | {\n type: \"item\";\n id: string;\n label: ReactNode;\n icon?: ReactNode;\n danger?: boolean;\n disabled?: boolean;\n onSelect: () => void;\n }\n | { type: \"separator\"; id: string }\n | { type: \"label\"; id: string; label: ReactNode };\n\nexport interface DropdownMenuProps {\n trigger: ReactElement<{\n onClick?: (e: React.MouseEvent) => void;\n \"aria-expanded\"?: boolean;\n \"aria-controls\"?: string;\n \"aria-haspopup\"?: boolean | \"menu\";\n }>;\n items: DropdownMenuEntry[];\n placement?: DropdownMenuPlacement;\n className?: string;\n}\n\nfunction placementClass(placement: DropdownMenuPlacement): string {\n switch (placement) {\n case \"bottom-end\":\n return styles.bottomEnd;\n case \"top-start\":\n return styles.topStart;\n case \"top-end\":\n return styles.topEnd;\n case \"bottom-start\":\n default:\n return styles.bottomStart;\n }\n}\n\n/**\n * Dropdown menu — list of selectable actions anchored to a trigger.\n *\n * - Toggle on trigger click.\n * - Close on outside click / Escape / item selection.\n * - Arrow keys navigate, Enter activates focused item.\n */\nexport function DropdownMenu({\n trigger,\n items,\n placement = \"bottom-start\",\n className,\n}: DropdownMenuProps) {\n const [open, setOpen] = useState(false);\n const [activeIndex, setActiveIndex] = useState<number>(-1);\n const id = useId();\n const rootRef = useRef<HTMLSpanElement>(null);\n const itemRefs = useRef<Array<HTMLButtonElement | null>>([]);\n\n const selectableIndexes = items\n .map((entry, index) => (entry.type === \"item\" && !entry.disabled ? index : -1))\n .filter((i) => i !== -1);\n\n const close = useCallback((): void => {\n setOpen(false);\n setActiveIndex(-1);\n }, []);\n\n useEffect(() => {\n if (!open) return;\n const onKey = (event: KeyboardEvent): void => {\n if (event.key === \"Escape\") {\n close();\n return;\n }\n if (event.key === \"ArrowDown\") {\n event.preventDefault();\n const current = selectableIndexes.indexOf(activeIndex);\n const next = selectableIndexes[(current + 1) % selectableIndexes.length] ?? -1;\n setActiveIndex(next);\n itemRefs.current[next]?.focus();\n }\n if (event.key === \"ArrowUp\") {\n event.preventDefault();\n const current = selectableIndexes.indexOf(activeIndex);\n const prev =\n selectableIndexes[\n (current - 1 + selectableIndexes.length) % selectableIndexes.length\n ] ?? -1;\n setActiveIndex(prev);\n itemRefs.current[prev]?.focus();\n }\n };\n const onDown = (event: MouseEvent): void => {\n if (rootRef.current && !rootRef.current.contains(event.target as Node)) close();\n };\n window.addEventListener(\"keydown\", onKey);\n window.addEventListener(\"mousedown\", onDown);\n return () => {\n window.removeEventListener(\"keydown\", onKey);\n window.removeEventListener(\"mousedown\", onDown);\n };\n }, [open, activeIndex, selectableIndexes, close]);\n\n const handleTriggerClick = (event: React.MouseEvent): void => {\n trigger.props.onClick?.(event);\n setOpen((prev) => !prev);\n };\n\n const triggerClone = {\n ...trigger,\n props: {\n ...trigger.props,\n onClick: handleTriggerClick,\n \"aria-expanded\": open,\n \"aria-controls\": id,\n \"aria-haspopup\": \"menu\" as const,\n },\n } as ReactElement;\n\n const handleSelect = (entry: Extract<DropdownMenuEntry, { type: \"item\" }>): void => {\n entry.onSelect();\n close();\n };\n\n return (\n <span ref={rootRef} className={styles.root}>\n {triggerClone}\n {open && (\n <ul\n id={id}\n role=\"menu\"\n className={cn(styles.menu, placementClass(placement), className)}\n >\n {items.map((entry, index) => {\n if (entry.type === \"separator\") {\n return (\n <li\n key={entry.id}\n role=\"separator\"\n className={styles.separator}\n aria-hidden\n />\n );\n }\n if (entry.type === \"label\") {\n return (\n <li key={entry.id} role=\"presentation\" className={styles.label}>\n {entry.label}\n </li>\n );\n }\n return (\n <li key={entry.id} role=\"none\">\n <button\n ref={(el) => {\n itemRefs.current[index] = el;\n }}\n type=\"button\"\n role=\"menuitem\"\n className={cn(\n styles.item,\n entry.danger && styles.danger,\n activeIndex === index && styles.active,\n )}\n disabled={entry.disabled}\n onClick={() => handleSelect(entry)}\n onMouseEnter={() => setActiveIndex(index)}\n >\n {entry.icon && <span aria-hidden>{entry.icon}</span>}\n {entry.label}\n </button>\n </li>\n );\n })}\n </ul>\n )}\n </span>\n );\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Dropzone.cjs","names":[],"sources":["../../../src/components/Dropzone/Dropzone.tsx"],"sourcesContent":["
|
|
1
|
+
{"version":3,"file":"Dropzone.cjs","names":[],"sources":["../../../src/components/Dropzone/Dropzone.tsx"],"sourcesContent":["/**\n * @tempest-limits props-count, function-lines — accept, multiple and maxSize are\n * validation the component must apply before it can call onDrop or onReject, and\n * drag state, keyboard activation and the hidden input all live in the body because\n * they resolve to the same file list.\n */\nimport { useRef, useState } from \"react\";\nimport type { DragEvent, KeyboardEvent, ReactNode } from \"react\";\nimport { cn } from \"@/utils/cn\";\nimport styles from \"./Dropzone.module.css\";\n\n/** Props for {@link Dropzone}. */\nexport interface DropzoneProps {\n /** Called with the accepted files after a drop or file-dialog selection. */\n onDrop: (files: File[]) => void;\n /** `accept` attribute forwarded to the hidden file input. */\n accept?: string;\n /** Allow selecting/dropping multiple files. Default `true`. */\n multiple?: boolean;\n /** Disable interaction. */\n disabled?: boolean;\n /** Maximum file size in bytes; larger files are filtered out. */\n maxSize?: number;\n /** Called with files rejected by `maxSize`. */\n onReject?: (files: File[]) => void;\n /** Custom inner content. Falls back to a default prompt. */\n children?: ReactNode;\n /** Extra class applied to the drop area. */\n className?: string;\n}\n\n/**\n * Drag-and-drop file area with a hidden file input. Clickable and keyboard\n * focusable; filters by `maxSize` before calling `onDrop`.\n *\n * @example\n * <Dropzone accept=\"image/*\" maxSize={5 * 1024 * 1024} onDrop={setFiles}>\n * Solte imagens aqui ou clique para selecionar\n * </Dropzone>\n */\nexport function Dropzone({\n onDrop,\n accept,\n multiple = true,\n disabled = false,\n maxSize,\n onReject,\n children,\n className,\n}: DropzoneProps) {\n const inputRef = useRef<HTMLInputElement>(null);\n const [isDragging, setIsDragging] = useState(false);\n\n const handleFiles = (fileList: FileList | null): void => {\n if (!fileList || fileList.length === 0) return;\n const all = Array.from(fileList);\n const limited = multiple ? all : all.slice(0, 1);\n\n if (maxSize === undefined) {\n onDrop(limited);\n return;\n }\n\n const accepted: File[] = [];\n const rejected: File[] = [];\n for (const file of limited) {\n if (file.size > maxSize) rejected.push(file);\n else accepted.push(file);\n }\n if (rejected.length > 0) onReject?.(rejected);\n if (accepted.length > 0) onDrop(accepted);\n };\n\n const openDialog = (): void => {\n if (disabled) return;\n inputRef.current?.click();\n };\n\n const handleDragEnter = (event: DragEvent<HTMLDivElement>): void => {\n event.preventDefault();\n if (disabled) return;\n setIsDragging(true);\n };\n\n const handleDragOver = (event: DragEvent<HTMLDivElement>): void => {\n event.preventDefault();\n };\n\n const handleDragLeave = (event: DragEvent<HTMLDivElement>): void => {\n event.preventDefault();\n setIsDragging(false);\n };\n\n const handleDrop = (event: DragEvent<HTMLDivElement>): void => {\n event.preventDefault();\n setIsDragging(false);\n if (disabled) return;\n handleFiles(event.dataTransfer.files);\n };\n\n const handleKeyDown = (event: KeyboardEvent<HTMLDivElement>): void => {\n if (event.key === \"Enter\" || event.key === \" \") {\n event.preventDefault();\n openDialog();\n }\n };\n\n return (\n <div\n role=\"button\"\n tabIndex={disabled ? -1 : 0}\n aria-disabled={disabled || undefined}\n className={cn(\n styles.zone,\n isDragging && styles.dragging,\n disabled && styles.disabled,\n className,\n )}\n onClick={openDialog}\n onKeyDown={handleKeyDown}\n onDragEnter={handleDragEnter}\n onDragOver={handleDragOver}\n onDragLeave={handleDragLeave}\n onDrop={handleDrop}\n >\n <input\n ref={inputRef}\n type=\"file\"\n className={styles.input}\n accept={accept}\n multiple={multiple}\n disabled={disabled}\n onChange={(event) => {\n handleFiles(event.target.files);\n event.target.value = \"\";\n }}\n />\n {children ?? (\n <span className={styles.prompt}>\n Arraste arquivos aqui ou clique para selecionar\n </span>\n )}\n </div>\n );\n}\n"],"mappings":"+HAwCA,SAAgB,EAAS,CACrB,SACA,SACA,WAAW,GACX,WAAW,GACX,UACA,WACA,WACA,aACc,CACd,IAAM,GAAA,EAAA,EAAA,OAAA,CAAoC,IAAI,EACxC,CAAC,EAAY,IAAA,EAAA,EAAA,SAAA,CAA0B,EAAK,EAE5C,EAAe,GAAoC,CACrD,GAAI,CAAC,GAAY,EAAS,SAAW,EAAG,OACxC,IAAM,EAAM,MAAM,KAAK,CAAQ,EACzB,EAAU,EAAW,EAAM,EAAI,MAAM,EAAG,CAAC,EAE/C,GAAI,IAAY,IAAA,GAAW,CACvB,EAAO,CAAO,EACd,MACJ,CAEA,IAAM,EAAmB,CAAC,EACpB,EAAmB,CAAC,EAC1B,IAAK,IAAM,KAAQ,EACX,EAAK,KAAO,EAAS,EAAS,KAAK,CAAI,EACtC,EAAS,KAAK,CAAI,EAEvB,EAAS,OAAS,GAAG,IAAW,CAAQ,EACxC,EAAS,OAAS,GAAG,EAAO,CAAQ,CAC5C,EAEM,MAAyB,CACvB,GACJ,EAAS,SAAS,MAAM,CAC5B,EA+BA,OACI,EAAA,EAAA,KAAA,CAAC,MAAD,CACI,KAAK,SACL,SAAU,EAAW,GAAK,EAC1B,gBAAe,GAAY,IAAA,GAC3B,UAAW,EAAA,GACP,EAAA,QAAO,KACP,GAAc,EAAA,QAAO,SACrB,GAAY,EAAA,QAAO,SACnB,CACJ,EACA,QAAS,EACT,UAnBe,GAA+C,EAC9D,EAAM,MAAQ,SAAW,EAAM,MAAQ,OACvC,EAAM,eAAe,EACrB,EAAW,EAEnB,EAeQ,YA1CiB,GAA2C,CAChE,EAAM,eAAe,EACjB,IACJ,EAAc,EAAI,CACtB,EAuCQ,WArCgB,GAA2C,CAC/D,EAAM,eAAe,CACzB,EAoCQ,YAlCiB,GAA2C,CAChE,EAAM,eAAe,EACrB,EAAc,EAAK,CACvB,EAgCQ,OA9BY,GAA2C,CAC3D,EAAM,eAAe,EACrB,EAAc,EAAK,EACf,IACJ,EAAY,EAAM,aAAa,KAAK,CACxC,WAUI,EAiBI,EAAA,EAAA,IAAA,CAAC,QAAD,CACI,IAAK,EACL,KAAK,OACL,UAAW,EAAA,QAAO,MACV,SACE,WACA,WACV,SAAW,GAAU,CACjB,EAAY,EAAM,OAAO,KAAK,EAC9B,EAAM,OAAO,MAAQ,EACzB,CACH,CAAA,EACA,IACG,EAAA,EAAA,IAAA,CAAC,OAAD,CAAM,UAAW,EAAA,QAAO,gBAAQ,iDAE1B,CAAA,CAET,GAEb"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Dropzone.js","names":[],"sources":["../../../src/components/Dropzone/Dropzone.tsx"],"sourcesContent":["
|
|
1
|
+
{"version":3,"file":"Dropzone.js","names":[],"sources":["../../../src/components/Dropzone/Dropzone.tsx"],"sourcesContent":["/**\n * @tempest-limits props-count, function-lines — accept, multiple and maxSize are\n * validation the component must apply before it can call onDrop or onReject, and\n * drag state, keyboard activation and the hidden input all live in the body because\n * they resolve to the same file list.\n */\nimport { useRef, useState } from \"react\";\nimport type { DragEvent, KeyboardEvent, ReactNode } from \"react\";\nimport { cn } from \"@/utils/cn\";\nimport styles from \"./Dropzone.module.css\";\n\n/** Props for {@link Dropzone}. */\nexport interface DropzoneProps {\n /** Called with the accepted files after a drop or file-dialog selection. */\n onDrop: (files: File[]) => void;\n /** `accept` attribute forwarded to the hidden file input. */\n accept?: string;\n /** Allow selecting/dropping multiple files. Default `true`. */\n multiple?: boolean;\n /** Disable interaction. */\n disabled?: boolean;\n /** Maximum file size in bytes; larger files are filtered out. */\n maxSize?: number;\n /** Called with files rejected by `maxSize`. */\n onReject?: (files: File[]) => void;\n /** Custom inner content. Falls back to a default prompt. */\n children?: ReactNode;\n /** Extra class applied to the drop area. */\n className?: string;\n}\n\n/**\n * Drag-and-drop file area with a hidden file input. Clickable and keyboard\n * focusable; filters by `maxSize` before calling `onDrop`.\n *\n * @example\n * <Dropzone accept=\"image/*\" maxSize={5 * 1024 * 1024} onDrop={setFiles}>\n * Solte imagens aqui ou clique para selecionar\n * </Dropzone>\n */\nexport function Dropzone({\n onDrop,\n accept,\n multiple = true,\n disabled = false,\n maxSize,\n onReject,\n children,\n className,\n}: DropzoneProps) {\n const inputRef = useRef<HTMLInputElement>(null);\n const [isDragging, setIsDragging] = useState(false);\n\n const handleFiles = (fileList: FileList | null): void => {\n if (!fileList || fileList.length === 0) return;\n const all = Array.from(fileList);\n const limited = multiple ? all : all.slice(0, 1);\n\n if (maxSize === undefined) {\n onDrop(limited);\n return;\n }\n\n const accepted: File[] = [];\n const rejected: File[] = [];\n for (const file of limited) {\n if (file.size > maxSize) rejected.push(file);\n else accepted.push(file);\n }\n if (rejected.length > 0) onReject?.(rejected);\n if (accepted.length > 0) onDrop(accepted);\n };\n\n const openDialog = (): void => {\n if (disabled) return;\n inputRef.current?.click();\n };\n\n const handleDragEnter = (event: DragEvent<HTMLDivElement>): void => {\n event.preventDefault();\n if (disabled) return;\n setIsDragging(true);\n };\n\n const handleDragOver = (event: DragEvent<HTMLDivElement>): void => {\n event.preventDefault();\n };\n\n const handleDragLeave = (event: DragEvent<HTMLDivElement>): void => {\n event.preventDefault();\n setIsDragging(false);\n };\n\n const handleDrop = (event: DragEvent<HTMLDivElement>): void => {\n event.preventDefault();\n setIsDragging(false);\n if (disabled) return;\n handleFiles(event.dataTransfer.files);\n };\n\n const handleKeyDown = (event: KeyboardEvent<HTMLDivElement>): void => {\n if (event.key === \"Enter\" || event.key === \" \") {\n event.preventDefault();\n openDialog();\n }\n };\n\n return (\n <div\n role=\"button\"\n tabIndex={disabled ? -1 : 0}\n aria-disabled={disabled || undefined}\n className={cn(\n styles.zone,\n isDragging && styles.dragging,\n disabled && styles.disabled,\n className,\n )}\n onClick={openDialog}\n onKeyDown={handleKeyDown}\n onDragEnter={handleDragEnter}\n onDragOver={handleDragOver}\n onDragLeave={handleDragLeave}\n onDrop={handleDrop}\n >\n <input\n ref={inputRef}\n type=\"file\"\n className={styles.input}\n accept={accept}\n multiple={multiple}\n disabled={disabled}\n onChange={(event) => {\n handleFiles(event.target.files);\n event.target.value = \"\";\n }}\n />\n {children ?? (\n <span className={styles.prompt}>\n Arraste arquivos aqui ou clique para selecionar\n </span>\n )}\n </div>\n );\n}\n"],"mappings":";;;;;AAwCA,SAAgB,EAAS,EACrB,WACA,WACA,cAAW,IACX,cAAW,IACX,YACA,aACA,aACA,gBACc;CACd,IAAM,IAAW,EAAyB,IAAI,GACxC,CAAC,GAAY,KAAiB,EAAS,EAAK,GAE5C,KAAe,MAAoC;EACrD,IAAI,CAAC,KAAY,EAAS,WAAW,GAAG;EACxC,IAAM,IAAM,MAAM,KAAK,CAAQ,GACzB,IAAU,IAAW,IAAM,EAAI,MAAM,GAAG,CAAC;EAE/C,IAAI,MAAY,KAAA,GAAW;GACvB,EAAO,CAAO;GACd;EACJ;EAEA,IAAM,IAAmB,CAAC,GACpB,IAAmB,CAAC;EAC1B,KAAK,IAAM,KAAQ,GACf,AAAI,EAAK,OAAO,IAAS,EAAS,KAAK,CAAI,IACtC,EAAS,KAAK,CAAI;EAG3B,AADI,EAAS,SAAS,KAAG,IAAW,CAAQ,GACxC,EAAS,SAAS,KAAG,EAAO,CAAQ;CAC5C,GAEM,UAAyB;EACvB,KACJ,EAAS,SAAS,MAAM;CAC5B;CA+BA,OACI,kBAAC,OAAD;EACI,MAAK;EACL,UAAU,IAAW,KAAK;EAC1B,iBAAe,KAAY,KAAA;EAC3B,WAAW,EACP,EAAO,MACP,KAAc,EAAO,UACrB,KAAY,EAAO,UACnB,CACJ;EACA,SAAS;EACT,YAnBe,MAA+C;GAClE,CAAI,EAAM,QAAQ,WAAW,EAAM,QAAQ,SACvC,EAAM,eAAe,GACrB,EAAW;EAEnB;EAeQ,cA1CiB,MAA2C;GAChE,EAAM,eAAe,GACjB,MACJ,EAAc,EAAI;EACtB;EAuCQ,aArCgB,MAA2C;GAC/D,EAAM,eAAe;EACzB;EAoCQ,cAlCiB,MAA2C;GAEhE,AADA,EAAM,eAAe,GACrB,EAAc,EAAK;EACvB;EAgCQ,SA9BY,MAA2C;GAC3D,EAAM,eAAe,GACrB,EAAc,EAAK,GACf,MACJ,EAAY,EAAM,aAAa,KAAK;EACxC;YAUI,CAiBI,kBAAC,SAAD;GACI,KAAK;GACL,MAAK;GACL,WAAW,EAAO;GACV;GACE;GACA;GACV,WAAW,MAAU;IAEjB,AADA,EAAY,EAAM,OAAO,KAAK,GAC9B,EAAM,OAAO,QAAQ;GACzB;EACH,CAAA,GACA,KACG,kBAAC,QAAD;GAAM,WAAW,EAAO;aAAQ;EAE1B,CAAA,CAET;;AAEb"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"FileUpload.cjs","names":[],"sources":["../../../src/components/FileUpload/FileUpload.tsx"],"sourcesContent":["import { useRef, useState } from \"react\";\nimport type { DragEvent } from \"react\";\nimport { cn } from \"@/utils/cn\";\nimport styles from \"./FileUpload.module.css\";\n\nexport interface FileUploadProps {\n /** Files currently in the dropzone (controlled). */\n value: File[];\n /** Called with the new list when the user adds or removes a file. */\n onChange: (files: File[]) => void;\n label?: string;\n /** Accept attribute forwarded to `<input>`. */\n accept?: string;\n /** Allow multiple files. Default: false. */\n multiple?: boolean;\n /** Max file size in bytes. Files above are rejected via `onReject`. */\n maxSize?: number;\n /** Called with rejected files when they exceed `maxSize` or fail the `accept` filter. */\n onReject?: (rejected: { file: File; reason: \"size\" | \"type\" }[]) => void;\n disabled?: boolean;\n /** Title shown on the empty dropzone. */\n title?: string;\n /** Helper line below the title. */\n subtitle?: string;\n className?: string;\n}\n\nfunction formatSize(bytes: number): string {\n if (bytes < 1024) return `${bytes} B`;\n if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;\n return `${(bytes / 1024 / 1024).toFixed(1)} MB`;\n}\n\nfunction matchesAccept(file: File, accept?: string): boolean {\n if (!accept) return true;\n const tokens = accept.split(\",\").map((t) => t.trim().toLowerCase());\n const name = file.name.toLowerCase();\n const type = file.type.toLowerCase();\n return tokens.some((token) => {\n if (token.startsWith(\".\")) return name.endsWith(token);\n if (token.endsWith(\"/*\")) return type.startsWith(token.replace(\"/*\", \"/\"));\n return type === token;\n });\n}\n\n/**\n * Drag-and-drop file dropzone. Pair with `uploadWithProgress` from the SDK\n * to wire actual uploads with byte-level progress.\n */\nexport function FileUpload({\n value,\n onChange,\n label,\n accept,\n multiple = false,\n maxSize,\n onReject,\n disabled,\n title = \"Arraste arquivos aqui ou clique para selecionar\",\n subtitle,\n className,\n}: FileUploadProps) {\n const [dragging, setDragging] = useState<boolean>(false);\n const inputRef = useRef<HTMLInputElement>(null);\n\n function addFiles(incoming: FileList | File[]): void {\n const accepted: File[] = [];\n const rejected: { file: File; reason: \"size\" | \"type\" }[] = [];\n\n for (const file of Array.from(incoming)) {\n if (!matchesAccept(file, accept)) {\n rejected.push({ file, reason: \"type\" });\n continue;\n }\n if (maxSize && file.size > maxSize) {\n rejected.push({ file, reason: \"size\" });\n continue;\n }\n accepted.push(file);\n }\n\n if (rejected.length > 0) onReject?.(rejected);\n if (accepted.length === 0) return;\n\n const next = multiple ? [...value, ...accepted] : accepted.slice(0, 1);\n onChange(next);\n }\n\n function handleDrop(event: DragEvent<HTMLDivElement>): void {\n event.preventDefault();\n setDragging(false);\n if (disabled) return;\n if (event.dataTransfer?.files) addFiles(event.dataTransfer.files);\n }\n\n function remove(index: number): void {\n const next = value.slice();\n next.splice(index, 1);\n onChange(next);\n }\n\n return (\n <div className={cn(styles.wrapper, className)}>\n {label && <label className={styles.label}>{label}</label>}\n <div\n className={cn(\n styles.dropzone,\n dragging && styles.active,\n disabled && styles.disabled,\n )}\n role=\"button\"\n tabIndex={disabled ? -1 : 0}\n onClick={() => !disabled && inputRef.current?.click()}\n onKeyDown={(event) => {\n if (event.key === \"Enter\" || event.key === \" \") {\n event.preventDefault();\n inputRef.current?.click();\n }\n }}\n onDragEnter={(event) => {\n event.preventDefault();\n if (!disabled) setDragging(true);\n }}\n onDragOver={(event) => event.preventDefault()}\n onDragLeave={() => setDragging(false)}\n onDrop={handleDrop}\n >\n <div className={styles.icon} aria-hidden>\n <UploadIcon />\n </div>\n <p className={styles.title}>{title}</p>\n {subtitle && <p className={styles.subtitle}>{subtitle}</p>}\n <input\n ref={inputRef}\n type=\"file\"\n accept={accept}\n multiple={multiple}\n disabled={disabled}\n className={styles.hidden}\n onChange={(event) => {\n if (event.target.files) addFiles(event.target.files);\n event.target.value = \"\";\n }}\n />\n </div>\n\n {value.length > 0 && (\n <ul className={styles.files}>\n {value.map((file, index) => (\n <li key={`${file.name}-${index}`} className={styles.file}>\n <div>\n <div>{file.name}</div>\n <div className={styles.fileMeta}>{formatSize(file.size)}</div>\n </div>\n <button\n type=\"button\"\n aria-label={`Remover ${file.name}`}\n className={styles.remove}\n onClick={() => remove(index)}\n >\n ×\n </button>\n </li>\n ))}\n </ul>\n )}\n </div>\n );\n}\n\nfunction UploadIcon() {\n return (\n <svg width=\"32\" height=\"32\" viewBox=\"0 0 24 24\" fill=\"none\">\n <path\n d=\"M12 16V4m0 0l-4 4m4-4l4 4M4 20h16\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n />\n </svg>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"FileUpload.cjs","names":[],"sources":["../../../src/components/FileUpload/FileUpload.tsx"],"sourcesContent":["/**\n * @tempest-limits file-lines, props-count, function-lines — the picker (accept,\n * multiple, maxSize, onReject) and the list of chosen files (value, onChange,\n * disabled) are one control, and title/subtitle/label exist because the empty state\n * is the biggest part of the surface.\n */\nimport { useRef, useState } from \"react\";\nimport type { DragEvent } from \"react\";\nimport { cn } from \"@/utils/cn\";\nimport styles from \"./FileUpload.module.css\";\n\nexport interface FileUploadProps {\n /** Files currently in the dropzone (controlled). */\n value: File[];\n /** Called with the new list when the user adds or removes a file. */\n onChange: (files: File[]) => void;\n label?: string;\n /** Accept attribute forwarded to `<input>`. */\n accept?: string;\n /** Allow multiple files. Default: false. */\n multiple?: boolean;\n /** Max file size in bytes. Files above are rejected via `onReject`. */\n maxSize?: number;\n /** Called with rejected files when they exceed `maxSize` or fail the `accept` filter. */\n onReject?: (rejected: { file: File; reason: \"size\" | \"type\" }[]) => void;\n disabled?: boolean;\n /** Title shown on the empty dropzone. */\n title?: string;\n /** Helper line below the title. */\n subtitle?: string;\n className?: string;\n}\n\nfunction formatSize(bytes: number): string {\n if (bytes < 1024) return `${bytes} B`;\n if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;\n return `${(bytes / 1024 / 1024).toFixed(1)} MB`;\n}\n\nfunction matchesAccept(file: File, accept?: string): boolean {\n if (!accept) return true;\n const tokens = accept.split(\",\").map((t) => t.trim().toLowerCase());\n const name = file.name.toLowerCase();\n const type = file.type.toLowerCase();\n return tokens.some((token) => {\n if (token.startsWith(\".\")) return name.endsWith(token);\n if (token.endsWith(\"/*\")) return type.startsWith(token.replace(\"/*\", \"/\"));\n return type === token;\n });\n}\n\n/**\n * Drag-and-drop file dropzone. Pair with `uploadWithProgress` from the SDK\n * to wire actual uploads with byte-level progress.\n */\nexport function FileUpload({\n value,\n onChange,\n label,\n accept,\n multiple = false,\n maxSize,\n onReject,\n disabled,\n title = \"Arraste arquivos aqui ou clique para selecionar\",\n subtitle,\n className,\n}: FileUploadProps) {\n const [dragging, setDragging] = useState<boolean>(false);\n const inputRef = useRef<HTMLInputElement>(null);\n\n function addFiles(incoming: FileList | File[]): void {\n const accepted: File[] = [];\n const rejected: { file: File; reason: \"size\" | \"type\" }[] = [];\n\n for (const file of Array.from(incoming)) {\n if (!matchesAccept(file, accept)) {\n rejected.push({ file, reason: \"type\" });\n continue;\n }\n if (maxSize && file.size > maxSize) {\n rejected.push({ file, reason: \"size\" });\n continue;\n }\n accepted.push(file);\n }\n\n if (rejected.length > 0) onReject?.(rejected);\n if (accepted.length === 0) return;\n\n const next = multiple ? [...value, ...accepted] : accepted.slice(0, 1);\n onChange(next);\n }\n\n function handleDrop(event: DragEvent<HTMLDivElement>): void {\n event.preventDefault();\n setDragging(false);\n if (disabled) return;\n if (event.dataTransfer?.files) addFiles(event.dataTransfer.files);\n }\n\n function remove(index: number): void {\n const next = value.slice();\n next.splice(index, 1);\n onChange(next);\n }\n\n return (\n <div className={cn(styles.wrapper, className)}>\n {label && <label className={styles.label}>{label}</label>}\n <div\n className={cn(\n styles.dropzone,\n dragging && styles.active,\n disabled && styles.disabled,\n )}\n role=\"button\"\n tabIndex={disabled ? -1 : 0}\n onClick={() => !disabled && inputRef.current?.click()}\n onKeyDown={(event) => {\n if (event.key === \"Enter\" || event.key === \" \") {\n event.preventDefault();\n inputRef.current?.click();\n }\n }}\n onDragEnter={(event) => {\n event.preventDefault();\n if (!disabled) setDragging(true);\n }}\n onDragOver={(event) => event.preventDefault()}\n onDragLeave={() => setDragging(false)}\n onDrop={handleDrop}\n >\n <div className={styles.icon} aria-hidden>\n <UploadIcon />\n </div>\n <p className={styles.title}>{title}</p>\n {subtitle && <p className={styles.subtitle}>{subtitle}</p>}\n <input\n ref={inputRef}\n type=\"file\"\n accept={accept}\n multiple={multiple}\n disabled={disabled}\n className={styles.hidden}\n onChange={(event) => {\n if (event.target.files) addFiles(event.target.files);\n event.target.value = \"\";\n }}\n />\n </div>\n\n {value.length > 0 && (\n <ul className={styles.files}>\n {value.map((file, index) => (\n <li key={`${file.name}-${index}`} className={styles.file}>\n <div>\n <div>{file.name}</div>\n <div className={styles.fileMeta}>{formatSize(file.size)}</div>\n </div>\n <button\n type=\"button\"\n aria-label={`Remover ${file.name}`}\n className={styles.remove}\n onClick={() => remove(index)}\n >\n ×\n </button>\n </li>\n ))}\n </ul>\n )}\n </div>\n );\n}\n\nfunction UploadIcon() {\n return (\n <svg width=\"32\" height=\"32\" viewBox=\"0 0 24 24\" fill=\"none\">\n <path\n d=\"M12 16V4m0 0l-4 4m4-4l4 4M4 20h16\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n />\n </svg>\n );\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"FileUpload.js","names":[],"sources":["../../../src/components/FileUpload/FileUpload.tsx"],"sourcesContent":["
|
|
1
|
+
{"version":3,"file":"FileUpload.js","names":[],"sources":["../../../src/components/FileUpload/FileUpload.tsx"],"sourcesContent":["/**\n * @tempest-limits file-lines, props-count, function-lines — the picker (accept,\n * multiple, maxSize, onReject) and the list of chosen files (value, onChange,\n * disabled) are one control, and title/subtitle/label exist because the empty state\n * is the biggest part of the surface.\n */\nimport { useRef, useState } from \"react\";\nimport type { DragEvent } from \"react\";\nimport { cn } from \"@/utils/cn\";\nimport styles from \"./FileUpload.module.css\";\n\nexport interface FileUploadProps {\n /** Files currently in the dropzone (controlled). */\n value: File[];\n /** Called with the new list when the user adds or removes a file. */\n onChange: (files: File[]) => void;\n label?: string;\n /** Accept attribute forwarded to `<input>`. */\n accept?: string;\n /** Allow multiple files. Default: false. */\n multiple?: boolean;\n /** Max file size in bytes. Files above are rejected via `onReject`. */\n maxSize?: number;\n /** Called with rejected files when they exceed `maxSize` or fail the `accept` filter. */\n onReject?: (rejected: { file: File; reason: \"size\" | \"type\" }[]) => void;\n disabled?: boolean;\n /** Title shown on the empty dropzone. */\n title?: string;\n /** Helper line below the title. */\n subtitle?: string;\n className?: string;\n}\n\nfunction formatSize(bytes: number): string {\n if (bytes < 1024) return `${bytes} B`;\n if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;\n return `${(bytes / 1024 / 1024).toFixed(1)} MB`;\n}\n\nfunction matchesAccept(file: File, accept?: string): boolean {\n if (!accept) return true;\n const tokens = accept.split(\",\").map((t) => t.trim().toLowerCase());\n const name = file.name.toLowerCase();\n const type = file.type.toLowerCase();\n return tokens.some((token) => {\n if (token.startsWith(\".\")) return name.endsWith(token);\n if (token.endsWith(\"/*\")) return type.startsWith(token.replace(\"/*\", \"/\"));\n return type === token;\n });\n}\n\n/**\n * Drag-and-drop file dropzone. Pair with `uploadWithProgress` from the SDK\n * to wire actual uploads with byte-level progress.\n */\nexport function FileUpload({\n value,\n onChange,\n label,\n accept,\n multiple = false,\n maxSize,\n onReject,\n disabled,\n title = \"Arraste arquivos aqui ou clique para selecionar\",\n subtitle,\n className,\n}: FileUploadProps) {\n const [dragging, setDragging] = useState<boolean>(false);\n const inputRef = useRef<HTMLInputElement>(null);\n\n function addFiles(incoming: FileList | File[]): void {\n const accepted: File[] = [];\n const rejected: { file: File; reason: \"size\" | \"type\" }[] = [];\n\n for (const file of Array.from(incoming)) {\n if (!matchesAccept(file, accept)) {\n rejected.push({ file, reason: \"type\" });\n continue;\n }\n if (maxSize && file.size > maxSize) {\n rejected.push({ file, reason: \"size\" });\n continue;\n }\n accepted.push(file);\n }\n\n if (rejected.length > 0) onReject?.(rejected);\n if (accepted.length === 0) return;\n\n const next = multiple ? [...value, ...accepted] : accepted.slice(0, 1);\n onChange(next);\n }\n\n function handleDrop(event: DragEvent<HTMLDivElement>): void {\n event.preventDefault();\n setDragging(false);\n if (disabled) return;\n if (event.dataTransfer?.files) addFiles(event.dataTransfer.files);\n }\n\n function remove(index: number): void {\n const next = value.slice();\n next.splice(index, 1);\n onChange(next);\n }\n\n return (\n <div className={cn(styles.wrapper, className)}>\n {label && <label className={styles.label}>{label}</label>}\n <div\n className={cn(\n styles.dropzone,\n dragging && styles.active,\n disabled && styles.disabled,\n )}\n role=\"button\"\n tabIndex={disabled ? -1 : 0}\n onClick={() => !disabled && inputRef.current?.click()}\n onKeyDown={(event) => {\n if (event.key === \"Enter\" || event.key === \" \") {\n event.preventDefault();\n inputRef.current?.click();\n }\n }}\n onDragEnter={(event) => {\n event.preventDefault();\n if (!disabled) setDragging(true);\n }}\n onDragOver={(event) => event.preventDefault()}\n onDragLeave={() => setDragging(false)}\n onDrop={handleDrop}\n >\n <div className={styles.icon} aria-hidden>\n <UploadIcon />\n </div>\n <p className={styles.title}>{title}</p>\n {subtitle && <p className={styles.subtitle}>{subtitle}</p>}\n <input\n ref={inputRef}\n type=\"file\"\n accept={accept}\n multiple={multiple}\n disabled={disabled}\n className={styles.hidden}\n onChange={(event) => {\n if (event.target.files) addFiles(event.target.files);\n event.target.value = \"\";\n }}\n />\n </div>\n\n {value.length > 0 && (\n <ul className={styles.files}>\n {value.map((file, index) => (\n <li key={`${file.name}-${index}`} className={styles.file}>\n <div>\n <div>{file.name}</div>\n <div className={styles.fileMeta}>{formatSize(file.size)}</div>\n </div>\n <button\n type=\"button\"\n aria-label={`Remover ${file.name}`}\n className={styles.remove}\n onClick={() => remove(index)}\n >\n ×\n </button>\n </li>\n ))}\n </ul>\n )}\n </div>\n );\n}\n\nfunction UploadIcon() {\n return (\n <svg width=\"32\" height=\"32\" viewBox=\"0 0 24 24\" fill=\"none\">\n <path\n d=\"M12 16V4m0 0l-4 4m4-4l4 4M4 20h16\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n />\n </svg>\n );\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"FilterBar.cjs","names":[],"sources":["../../../src/components/FilterBar/FilterBar.tsx"],"sourcesContent":["import { useId, useState, type HTMLAttributes, type ReactNode } from \"react\";\n\nimport { cn } from \"@/utils/cn\";\n\nimport { VisuallyHidden } from \"../VisuallyHidden\";\nimport {\n defaultOperator,\n describeFilter,\n filterStrings,\n isComplete,\n isMulti,\n isValueless,\n operatorLabel,\n operatorsFor,\n type Filter,\n type FilterField,\n} from \"./filter-model\";\nimport styles from \"./FilterBar.module.css\";\n\n/** DOM attributes this component redefines. */\ntype OverriddenDomProps = \"children\" | \"onChange\";\n\nexport interface FilterBarProps extends Omit<HTMLAttributes<HTMLDivElement>, OverriddenDomProps> {\n /** Fields the user may filter by. */\n fields: readonly FilterField[];\n /** Applied filters. Controlled. */\n value: readonly Filter[];\n /** Next filter set. Combined with AND. */\n onChange: (filters: Filter[]) => void;\n /** Locale for the labels. Default `\"pt-BR\"`. */\n locale?: \"pt-BR\" | \"en\";\n /** Rendered at the end of the bar — a \"save this view\" button, a counter. */\n actions?: ReactNode;\n}\n\n/** The draft filter being built in the editor. */\ninterface Draft {\n field: string;\n operator: Filter[\"operator\"];\n value: string;\n to: string;\n}\n\n/** A fresh draft for a field. */\nfunction draftFor(field: FilterField): Draft {\n return { field: field.name, operator: defaultOperator(field), value: \"\", to: \"\" };\n}\n\n/** Turn a draft into a filter. */\nfunction toFilter(draft: Draft): Filter {\n if (isValueless(draft.operator)) return { field: draft.field, operator: draft.operator };\n if (draft.operator === \"between\") {\n return { field: draft.field, operator: draft.operator, value: [draft.value, draft.to] };\n }\n if (isMulti(draft.operator)) {\n return {\n field: draft.field,\n operator: draft.operator,\n value: draft.value\n .split(\",\")\n .map((part) => part.trim())\n .filter(Boolean),\n };\n }\n return { field: draft.field, operator: draft.operator, value: draft.value };\n}\n\n/**\n * A row of applied filters, and a small editor to add another.\n *\n * Filters are combined with **AND**, flat — no nested `(a OR b) AND c` groups. That\n * is the shape of nearly every admin list (\"status is paid, created after March,\n * text contains nota\"), and it is what serializes into a URL somebody can send to a\n * colleague. Nested groups need a tree UI with per-node operators and a different\n * serialization; a component trying to be both is clumsy at the common case.\n *\n * Controlled, like the rest of the SDK: it holds the half-typed draft in the editor\n * and nothing else. The applied set is the app's, which is what lets it come from —\n * and go back into — the URL.\n *\n * @example\n * const [filters, setFilters] = useState<Filter[]>(() =>\n * filtersFromSearchParams(new URLSearchParams(location.search), FIELDS),\n * );\n *\n * <FilterBar fields={FIELDS} value={filters} onChange={setFilters} />\n */\nexport function FilterBar({\n fields,\n value,\n onChange,\n locale = \"pt-BR\",\n actions,\n className,\n ...rest\n}: FilterBarProps) {\n const strings = filterStrings(locale);\n const baseId = useId();\n const [draft, setDraft] = useState<Draft | null>(null);\n\n const field = draft ? fields.find((candidate) => candidate.name === draft.field) : undefined;\n const pending = draft ? toFilter(draft) : null;\n const ready = pending !== null && isComplete(pending);\n\n const open = (): void => {\n if (fields.length > 0) setDraft(draftFor(fields[0]));\n };\n\n const apply = (): void => {\n if (!pending || !ready) return;\n onChange([...value, pending]);\n setDraft(null);\n };\n\n const removeAt = (index: number): void => {\n onChange(value.filter((_, position) => position !== index));\n };\n\n return (\n <div className={cn(styles.bar, className)} {...rest}>\n <ul className={styles.chips}>\n {value.length === 0 && !draft && <li className={styles.empty}>{strings.empty}</li>}\n {value.map((filter, index) => {\n const description = describeFilter(filter, fields, locale);\n return (\n <li\n key={`${filter.field}-${filter.operator}-${index}`}\n className={styles.chip}\n >\n <span>{description}</span>\n <button\n type=\"button\"\n className={styles.remove}\n aria-label={strings.remove(description)}\n onClick={() => removeAt(index)}\n >\n ×\n </button>\n </li>\n );\n })}\n </ul>\n\n {draft && field ? (\n <div className={styles.editor} role=\"group\" aria-label={strings.add}>\n <label className={styles.control}>\n <VisuallyHidden>{strings.field}</VisuallyHidden>\n <select\n className={styles.select}\n value={draft.field}\n onChange={(event) => {\n const next = fields.find(\n (candidate) => candidate.name === event.target.value,\n );\n if (next) setDraft(draftFor(next));\n }}\n >\n {fields.map((candidate) => (\n <option key={candidate.name} value={candidate.name}>\n {candidate.label}\n </option>\n ))}\n </select>\n </label>\n\n <label className={styles.control}>\n <VisuallyHidden>{strings.operator}</VisuallyHidden>\n <select\n className={styles.select}\n value={draft.operator}\n onChange={(event) =>\n setDraft({\n ...draft,\n operator: event.target.value as Filter[\"operator\"],\n value: \"\",\n to: \"\",\n })\n }\n >\n {operatorsFor(field).map((operator) => (\n <option key={operator} value={operator}>\n {operatorLabel(operator, locale)}\n </option>\n ))}\n </select>\n </label>\n\n {!isValueless(draft.operator) && (\n <ValueInput\n id={`${baseId}-value`}\n field={field}\n draft={draft}\n strings={strings}\n onChange={setDraft}\n />\n )}\n\n <button\n type=\"button\"\n className={styles.primary}\n disabled={!ready}\n onClick={apply}\n >\n {strings.apply}\n </button>\n <button type=\"button\" className={styles.ghost} onClick={() => setDraft(null)}>\n {strings.cancel}\n </button>\n </div>\n ) : (\n <div className={styles.actions}>\n <button\n type=\"button\"\n className={styles.secondary}\n disabled={fields.length === 0}\n onClick={open}\n >\n + {strings.add}\n </button>\n {value.length > 0 && (\n <button type=\"button\" className={styles.ghost} onClick={() => onChange([])}>\n {strings.clear}\n </button>\n )}\n {actions}\n </div>\n )}\n\n <VisuallyHidden aria-live=\"polite\" role=\"status\">\n {strings.active(value.length)}\n </VisuallyHidden>\n </div>\n );\n}\n\n/**\n * The value input for the draft.\n *\n * The input type follows the **field**, not the operator: a date field gets a date\n * picker even under `between`, where two of them are needed. Typing a date into a\n * text box is the fastest way to produce a filter the backend cannot parse.\n */\nfunction ValueInput({\n id,\n field,\n draft,\n strings,\n onChange,\n}: {\n id: string;\n field: FilterField;\n draft: Draft;\n strings: ReturnType<typeof filterStrings>;\n onChange: (draft: Draft) => void;\n}) {\n const type = field.type === \"number\" ? \"number\" : field.type === \"date\" ? \"date\" : \"text\";\n\n if (field.type === \"select\" && !isMulti(draft.operator)) {\n return (\n <label className={styles.control}>\n <VisuallyHidden>{strings.value}</VisuallyHidden>\n <select\n id={id}\n className={styles.select}\n value={draft.value}\n onChange={(event) => onChange({ ...draft, value: event.target.value })}\n >\n <option value=\"\">—</option>\n {field.options?.map((option) => (\n <option key={option.value} value={option.value}>\n {option.label}\n </option>\n ))}\n </select>\n </label>\n );\n }\n\n if (field.type === \"boolean\") {\n return (\n <label className={styles.control}>\n <VisuallyHidden>{strings.value}</VisuallyHidden>\n <select\n id={id}\n className={styles.select}\n value={draft.value}\n onChange={(event) => onChange({ ...draft, value: event.target.value })}\n >\n <option value=\"\">—</option>\n <option value=\"true\">{strings.yes}</option>\n <option value=\"false\">{strings.no}</option>\n </select>\n </label>\n );\n }\n\n if (draft.operator === \"between\") {\n return (\n <>\n <label className={styles.control}>\n <VisuallyHidden>{strings.from}</VisuallyHidden>\n <input\n id={id}\n className={styles.input}\n type={type}\n value={draft.value}\n placeholder={strings.from}\n onChange={(event) => onChange({ ...draft, value: event.target.value })}\n />\n </label>\n <label className={styles.control}>\n <VisuallyHidden>{strings.to}</VisuallyHidden>\n <input\n className={styles.input}\n type={type}\n value={draft.to}\n placeholder={strings.to}\n onChange={(event) => onChange({ ...draft, to: event.target.value })}\n />\n </label>\n </>\n );\n }\n\n return (\n <label className={styles.control}>\n <VisuallyHidden>{strings.value}</VisuallyHidden>\n <input\n id={id}\n className={styles.input}\n type={type}\n value={draft.value}\n placeholder={field.placeholder ?? strings.value}\n onChange={(event) => onChange({ ...draft, value: event.target.value })}\n />\n </label>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"FilterBar.cjs","names":[],"sources":["../../../src/components/FilterBar/FilterBar.tsx"],"sourcesContent":["/**\n * @tempest-limits file-lines, function-lines — a filter bar renders a different\n * editor per filter kind — select, multi-select, range, date range, text — and the\n * body is that dispatch plus the chip summary, both reading the same value map.\n */\nimport { useId, useState, type HTMLAttributes, type ReactNode } from \"react\";\n\nimport { cn } from \"@/utils/cn\";\n\nimport { VisuallyHidden } from \"../VisuallyHidden\";\nimport {\n defaultOperator,\n describeFilter,\n filterStrings,\n isComplete,\n isMulti,\n isValueless,\n operatorLabel,\n operatorsFor,\n type Filter,\n type FilterField,\n} from \"./filter-model\";\nimport styles from \"./FilterBar.module.css\";\n\n/** DOM attributes this component redefines. */\ntype OverriddenDomProps = \"children\" | \"onChange\";\n\nexport interface FilterBarProps extends Omit<HTMLAttributes<HTMLDivElement>, OverriddenDomProps> {\n /** Fields the user may filter by. */\n fields: readonly FilterField[];\n /** Applied filters. Controlled. */\n value: readonly Filter[];\n /** Next filter set. Combined with AND. */\n onChange: (filters: Filter[]) => void;\n /** Locale for the labels. Default `\"pt-BR\"`. */\n locale?: \"pt-BR\" | \"en\";\n /** Rendered at the end of the bar — a \"save this view\" button, a counter. */\n actions?: ReactNode;\n}\n\n/** The draft filter being built in the editor. */\ninterface Draft {\n field: string;\n operator: Filter[\"operator\"];\n value: string;\n to: string;\n}\n\n/** A fresh draft for a field. */\nfunction draftFor(field: FilterField): Draft {\n return { field: field.name, operator: defaultOperator(field), value: \"\", to: \"\" };\n}\n\n/** Turn a draft into a filter. */\nfunction toFilter(draft: Draft): Filter {\n if (isValueless(draft.operator)) return { field: draft.field, operator: draft.operator };\n if (draft.operator === \"between\") {\n return { field: draft.field, operator: draft.operator, value: [draft.value, draft.to] };\n }\n if (isMulti(draft.operator)) {\n return {\n field: draft.field,\n operator: draft.operator,\n value: draft.value\n .split(\",\")\n .map((part) => part.trim())\n .filter(Boolean),\n };\n }\n return { field: draft.field, operator: draft.operator, value: draft.value };\n}\n\n/**\n * A row of applied filters, and a small editor to add another.\n *\n * Filters are combined with **AND**, flat — no nested `(a OR b) AND c` groups. That\n * is the shape of nearly every admin list (\"status is paid, created after March,\n * text contains nota\"), and it is what serializes into a URL somebody can send to a\n * colleague. Nested groups need a tree UI with per-node operators and a different\n * serialization; a component trying to be both is clumsy at the common case.\n *\n * Controlled, like the rest of the SDK: it holds the half-typed draft in the editor\n * and nothing else. The applied set is the app's, which is what lets it come from —\n * and go back into — the URL.\n *\n * @example\n * const [filters, setFilters] = useState<Filter[]>(() =>\n * filtersFromSearchParams(new URLSearchParams(location.search), FIELDS),\n * );\n *\n * <FilterBar fields={FIELDS} value={filters} onChange={setFilters} />\n */\nexport function FilterBar({\n fields,\n value,\n onChange,\n locale = \"pt-BR\",\n actions,\n className,\n ...rest\n}: FilterBarProps) {\n const strings = filterStrings(locale);\n const baseId = useId();\n const [draft, setDraft] = useState<Draft | null>(null);\n\n const field = draft ? fields.find((candidate) => candidate.name === draft.field) : undefined;\n const pending = draft ? toFilter(draft) : null;\n const ready = pending !== null && isComplete(pending);\n\n const open = (): void => {\n if (fields.length > 0) setDraft(draftFor(fields[0]));\n };\n\n const apply = (): void => {\n if (!pending || !ready) return;\n onChange([...value, pending]);\n setDraft(null);\n };\n\n const removeAt = (index: number): void => {\n onChange(value.filter((_, position) => position !== index));\n };\n\n return (\n <div className={cn(styles.bar, className)} {...rest}>\n <ul className={styles.chips}>\n {value.length === 0 && !draft && <li className={styles.empty}>{strings.empty}</li>}\n {value.map((filter, index) => {\n const description = describeFilter(filter, fields, locale);\n return (\n <li\n key={`${filter.field}-${filter.operator}-${index}`}\n className={styles.chip}\n >\n <span>{description}</span>\n <button\n type=\"button\"\n className={styles.remove}\n aria-label={strings.remove(description)}\n onClick={() => removeAt(index)}\n >\n ×\n </button>\n </li>\n );\n })}\n </ul>\n\n {draft && field ? (\n <div className={styles.editor} role=\"group\" aria-label={strings.add}>\n <label className={styles.control}>\n <VisuallyHidden>{strings.field}</VisuallyHidden>\n <select\n className={styles.select}\n value={draft.field}\n onChange={(event) => {\n const next = fields.find(\n (candidate) => candidate.name === event.target.value,\n );\n if (next) setDraft(draftFor(next));\n }}\n >\n {fields.map((candidate) => (\n <option key={candidate.name} value={candidate.name}>\n {candidate.label}\n </option>\n ))}\n </select>\n </label>\n\n <label className={styles.control}>\n <VisuallyHidden>{strings.operator}</VisuallyHidden>\n <select\n className={styles.select}\n value={draft.operator}\n onChange={(event) =>\n setDraft({\n ...draft,\n operator: event.target.value as Filter[\"operator\"],\n value: \"\",\n to: \"\",\n })\n }\n >\n {operatorsFor(field).map((operator) => (\n <option key={operator} value={operator}>\n {operatorLabel(operator, locale)}\n </option>\n ))}\n </select>\n </label>\n\n {!isValueless(draft.operator) && (\n <ValueInput\n id={`${baseId}-value`}\n field={field}\n draft={draft}\n strings={strings}\n onChange={setDraft}\n />\n )}\n\n <button\n type=\"button\"\n className={styles.primary}\n disabled={!ready}\n onClick={apply}\n >\n {strings.apply}\n </button>\n <button type=\"button\" className={styles.ghost} onClick={() => setDraft(null)}>\n {strings.cancel}\n </button>\n </div>\n ) : (\n <div className={styles.actions}>\n <button\n type=\"button\"\n className={styles.secondary}\n disabled={fields.length === 0}\n onClick={open}\n >\n + {strings.add}\n </button>\n {value.length > 0 && (\n <button type=\"button\" className={styles.ghost} onClick={() => onChange([])}>\n {strings.clear}\n </button>\n )}\n {actions}\n </div>\n )}\n\n <VisuallyHidden aria-live=\"polite\" role=\"status\">\n {strings.active(value.length)}\n </VisuallyHidden>\n </div>\n );\n}\n\n/**\n * The value input for the draft.\n *\n * The input type follows the **field**, not the operator: a date field gets a date\n * picker even under `between`, where two of them are needed. Typing a date into a\n * text box is the fastest way to produce a filter the backend cannot parse.\n */\nfunction ValueInput({\n id,\n field,\n draft,\n strings,\n onChange,\n}: {\n id: string;\n field: FilterField;\n draft: Draft;\n strings: ReturnType<typeof filterStrings>;\n onChange: (draft: Draft) => void;\n}) {\n const type = field.type === \"number\" ? \"number\" : field.type === \"date\" ? \"date\" : \"text\";\n\n if (field.type === \"select\" && !isMulti(draft.operator)) {\n return (\n <label className={styles.control}>\n <VisuallyHidden>{strings.value}</VisuallyHidden>\n <select\n id={id}\n className={styles.select}\n value={draft.value}\n onChange={(event) => onChange({ ...draft, value: event.target.value })}\n >\n <option value=\"\">—</option>\n {field.options?.map((option) => (\n <option key={option.value} value={option.value}>\n {option.label}\n </option>\n ))}\n </select>\n </label>\n );\n }\n\n if (field.type === \"boolean\") {\n return (\n <label className={styles.control}>\n <VisuallyHidden>{strings.value}</VisuallyHidden>\n <select\n id={id}\n className={styles.select}\n value={draft.value}\n onChange={(event) => onChange({ ...draft, value: event.target.value })}\n >\n <option value=\"\">—</option>\n <option value=\"true\">{strings.yes}</option>\n <option value=\"false\">{strings.no}</option>\n </select>\n </label>\n );\n }\n\n if (draft.operator === \"between\") {\n return (\n <>\n <label className={styles.control}>\n <VisuallyHidden>{strings.from}</VisuallyHidden>\n <input\n id={id}\n className={styles.input}\n type={type}\n value={draft.value}\n placeholder={strings.from}\n onChange={(event) => onChange({ ...draft, value: event.target.value })}\n />\n </label>\n <label className={styles.control}>\n <VisuallyHidden>{strings.to}</VisuallyHidden>\n <input\n className={styles.input}\n type={type}\n value={draft.to}\n placeholder={strings.to}\n onChange={(event) => onChange({ ...draft, to: event.target.value })}\n />\n </label>\n </>\n );\n }\n\n return (\n <label className={styles.control}>\n <VisuallyHidden>{strings.value}</VisuallyHidden>\n <input\n id={id}\n className={styles.input}\n type={type}\n value={draft.value}\n placeholder={field.placeholder ?? strings.value}\n onChange={(event) => onChange({ ...draft, value: event.target.value })}\n />\n </label>\n );\n}\n"],"mappings":"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"}
|