tempest-react-sdk 0.39.0 → 0.40.0
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/dist/_virtual/_rolldown/runtime.cjs +1 -1
- package/dist/access/access-control-context.cjs.map +1 -1
- package/dist/access/use-can.cjs.map +1 -1
- package/dist/app/AppProviders.cjs.map +1 -1
- package/dist/app/AppProviders.js.map +1 -1
- package/dist/audio/use-audio-recorder.cjs.map +1 -1
- package/dist/audio/use-audio.cjs.map +1 -1
- package/dist/audio/use-microphone.cjs.map +1 -1
- package/dist/auth/create-auth-store.cjs.map +1 -1
- package/dist/auth/lazy-with-retry.cjs.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/br/BrazilMap.cjs +1 -1
- package/dist/br/BrazilMap.cjs.map +1 -1
- package/dist/br/BrazilMap.js +2 -2
- 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 +1 -1
- package/dist/br/BrazilStateMap.cjs.map +1 -1
- package/dist/br/BrazilStateMap.js +2 -2
- package/dist/br/BrazilStateMap.js.map +1 -1
- package/dist/br/MapLegend.cjs.map +1 -1
- package/dist/br/MapLegend.js.map +1 -1
- package/dist/br/MapMarkers.cjs.map +1 -1
- package/dist/br/MapMarkers.js.map +1 -1
- package/dist/br/MunicipalitySearch.cjs.map +1 -1
- package/dist/br/MunicipalitySearch.js.map +1 -1
- package/dist/br/PixQRCode.cjs.map +1 -1
- package/dist/br/PixQRCode.js.map +1 -1
- package/dist/br/use-map-hover.cjs.map +1 -1
- package/dist/br/use-map-zoom.cjs.map +1 -1
- package/dist/capture/media-recorder.cjs +1 -1
- package/dist/capture/media-recorder.js +2 -2
- package/dist/capture/use-barcode-scanner.cjs +1 -1
- package/dist/capture/use-barcode-scanner.cjs.map +1 -1
- package/dist/capture/use-barcode-scanner.js +2 -2
- package/dist/capture/use-barcode-scanner.js.map +1 -1
- package/dist/capture/use-screen-capture.cjs.map +1 -1
- package/dist/capture/use-speech-recognition.cjs.map +1 -1
- package/dist/capture/use-torch.cjs.map +1 -1
- package/dist/capture/use-video-recorder.cjs.map +1 -1
- package/dist/charts/AreaChart.cjs.map +1 -1
- package/dist/charts/AreaChart.js.map +1 -1
- package/dist/charts/BarChart.cjs.map +1 -1
- package/dist/charts/BarChart.js.map +1 -1
- package/dist/charts/LineChart.cjs.map +1 -1
- package/dist/charts/LineChart.js.map +1 -1
- package/dist/charts/PieChart.cjs.map +1 -1
- package/dist/charts/PieChart.js.map +1 -1
- package/dist/charts/RadarChart.cjs.map +1 -1
- package/dist/charts/RadarChart.js.map +1 -1
- package/dist/charts/use-chart-colors.cjs.map +1 -1
- 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/Accordion/Accordion.cjs.map +1 -1
- package/dist/components/Accordion/Accordion.js.map +1 -1
- package/dist/components/Alert/Alert.cjs.map +1 -1
- package/dist/components/Alert/Alert.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/AppShell/AppShell.cjs.map +1 -1
- package/dist/components/AppShell/AppShell.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/Avatar/Avatar.cjs.map +1 -1
- package/dist/components/Avatar/Avatar.js.map +1 -1
- package/dist/components/AvatarGroup/AvatarGroup.cjs.map +1 -1
- package/dist/components/AvatarGroup/AvatarGroup.js.map +1 -1
- package/dist/components/Badge/Badge.cjs.map +1 -1
- package/dist/components/Badge/Badge.js.map +1 -1
- package/dist/components/Banner/Banner.cjs.map +1 -1
- package/dist/components/Banner/Banner.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/BottomNavigation/BottomNavigation.cjs.map +1 -1
- package/dist/components/BottomNavigation/BottomNavigation.js.map +1 -1
- package/dist/components/BottomSheet/BottomSheet.cjs.map +1 -1
- package/dist/components/BottomSheet/BottomSheet.js.map +1 -1
- package/dist/components/Breadcrumbs/Breadcrumbs.cjs.map +1 -1
- package/dist/components/Breadcrumbs/Breadcrumbs.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 +1 -1
- package/dist/components/Calendar/Calendar.cjs.map +1 -1
- package/dist/components/Calendar/Calendar.js +38 -41
- package/dist/components/Calendar/Calendar.js.map +1 -1
- package/dist/components/Card/Card.cjs.map +1 -1
- package/dist/components/Card/Card.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/Chat/ChatComposer.cjs.map +1 -1
- package/dist/components/Chat/ChatComposer.js.map +1 -1
- package/dist/components/Chat/chat-groups.cjs +1 -1
- package/dist/components/Chat/chat-groups.cjs.map +1 -1
- package/dist/components/Chat/chat-groups.js +2 -2
- package/dist/components/Chat/chat-groups.js.map +1 -1
- package/dist/components/Checkbox/Checkbox.cjs.map +1 -1
- package/dist/components/Checkbox/Checkbox.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/ClickOutside/ClickOutside.cjs.map +1 -1
- package/dist/components/CodeBlock/CodeBlock.cjs.map +1 -1
- package/dist/components/CodeBlock/CodeBlock.js.map +1 -1
- package/dist/components/Collapsible/Collapsible.cjs.map +1 -1
- package/dist/components/Collapsible/Collapsible.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/DataList/DataList.cjs.map +1 -1
- package/dist/components/DataList/DataList.js.map +1 -1
- package/dist/components/DataTable/DataTable.cjs +1 -1
- package/dist/components/DataTable/DataTable.cjs.map +1 -1
- package/dist/components/DataTable/DataTable.js +2 -2
- package/dist/components/DataTable/DataTable.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/DateRangePicker/DateRangePicker.cjs.map +1 -1
- package/dist/components/DateRangePicker/DateRangePicker.js.map +1 -1
- package/dist/components/DescriptionList/DescriptionList.cjs.map +1 -1
- package/dist/components/DescriptionList/DescriptionList.js.map +1 -1
- package/dist/components/Divider/Divider.cjs.map +1 -1
- package/dist/components/Divider/Divider.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/EmptyState/EmptyState.cjs.map +1 -1
- package/dist/components/EmptyState/EmptyState.js.map +1 -1
- package/dist/components/ErrorState/ErrorState.cjs.map +1 -1
- package/dist/components/ErrorState/ErrorState.js.map +1 -1
- package/dist/components/FileUpload/FileUpload.cjs +1 -1
- package/dist/components/FileUpload/FileUpload.cjs.map +1 -1
- package/dist/components/FileUpload/FileUpload.js +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/FloatingActionButton/FloatingActionButton.cjs.map +1 -1
- package/dist/components/FloatingActionButton/FloatingActionButton.js.map +1 -1
- package/dist/components/Form/Form.cjs.map +1 -1
- package/dist/components/Form/Form.js.map +1 -1
- package/dist/components/HoverCard/HoverCard.cjs.map +1 -1
- package/dist/components/HoverCard/HoverCard.js.map +1 -1
- package/dist/components/Image/Image.cjs.map +1 -1
- package/dist/components/ImageCropper/ImageCropper.cjs +1 -1
- package/dist/components/ImageCropper/ImageCropper.cjs.map +1 -1
- package/dist/components/ImageCropper/ImageCropper.js +1 -1
- package/dist/components/ImageCropper/ImageCropper.js.map +1 -1
- package/dist/components/Input/Input.cjs.map +1 -1
- package/dist/components/Input/Input.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/InstallButton/InstallButton.cjs.map +1 -1
- package/dist/components/InstallButton/InstallButton.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/Label/Label.cjs.map +1 -1
- package/dist/components/Label/Label.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 +1 -1
- package/dist/components/Markdown/Markdown.cjs.map +1 -1
- package/dist/components/Markdown/Markdown.js +2 -2
- package/dist/components/Markdown/Markdown.js.map +1 -1
- package/dist/components/Masonry/Masonry.cjs.map +1 -1
- package/dist/components/Masonry/Masonry.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/Money/Money.cjs.map +1 -1
- package/dist/components/Money/Money.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/NProgress/NProgress.cjs.map +1 -1
- package/dist/components/Navbar/Navbar.cjs.map +1 -1
- package/dist/components/Navbar/Navbar.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/NavigationRail/NavigationRail.cjs.map +1 -1
- package/dist/components/NavigationRail/NavigationRail.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/NotificationCenter/use-notification-inbox.cjs.map +1 -1
- package/dist/components/OfflineIndicator/OfflineIndicator.cjs.map +1 -1
- package/dist/components/OfflineIndicator/OfflineIndicator.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/Portal/Portal.cjs.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/QRCode.cjs.map +1 -1
- package/dist/components/QRCode/QRCode.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/RelativeTime/RelativeTime.cjs.map +1 -1
- package/dist/components/RelativeTime/RelativeTime.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/ScrollArea/ScrollArea.cjs.map +1 -1
- package/dist/components/SearchBar/SearchBar.cjs.map +1 -1
- package/dist/components/SearchBar/SearchBar.js.map +1 -1
- package/dist/components/SegmentedControl/SegmentedControl.cjs.map +1 -1
- package/dist/components/SegmentedControl/SegmentedControl.js.map +1 -1
- package/dist/components/Select/Select.cjs.map +1 -1
- package/dist/components/Select/Select.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/Spinner/Spinner.cjs.map +1 -1
- package/dist/components/Spinner/Spinner.js.map +1 -1
- package/dist/components/Stat/Stat.cjs.map +1 -1
- package/dist/components/Stat/Stat.js.map +1 -1
- package/dist/components/Stepper/Stepper.cjs.map +1 -1
- package/dist/components/Stepper/Stepper.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/Switch/Switch.cjs.map +1 -1
- package/dist/components/Switch/Switch.js.map +1 -1
- package/dist/components/SyncStatusBadge/SyncStatusBadge.cjs.map +1 -1
- package/dist/components/SyncStatusBadge/SyncStatusBadge.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/Tabs/Tabs.cjs.map +1 -1
- package/dist/components/Tabs/Tabs.js.map +1 -1
- package/dist/components/Tag/Tag.cjs.map +1 -1
- package/dist/components/Tag/Tag.js.map +1 -1
- package/dist/components/Textarea/Textarea.cjs.map +1 -1
- package/dist/components/Textarea/Textarea.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/Timeline/Timeline.cjs.map +1 -1
- package/dist/components/Timeline/Timeline.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/Toggle/Toggle.cjs.map +1 -1
- package/dist/components/ToggleGroup/ToggleGroup.cjs.map +1 -1
- package/dist/components/ToggleGroup/ToggleGroup.js.map +1 -1
- package/dist/components/Tooltip/Tooltip.cjs.map +1 -1
- package/dist/components/Tooltip/Tooltip.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 +1 -1
- package/dist/components/TreeView/TreeView.cjs.map +1 -1
- package/dist/components/TreeView/TreeView.js +1 -4
- package/dist/components/TreeView/TreeView.js.map +1 -1
- package/dist/components/UpdatePrompt/UpdatePrompt.cjs.map +1 -1
- package/dist/components/UpdatePrompt/UpdatePrompt.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/VisuallyHidden/VisuallyHidden.cjs.map +1 -1
- package/dist/components/Wizard/Wizard.cjs.map +1 -1
- package/dist/components/Wizard/Wizard.js.map +1 -1
- package/dist/data/data-provider-context.cjs.map +1 -1
- package/dist/data/use-resource.cjs.map +1 -1
- package/dist/editor/RichTextEditor.cjs.map +1 -1
- package/dist/editor/RichTextEditor.js.map +1 -1
- package/dist/error-boundary/use-error-handler.cjs.map +1 -1
- package/dist/feature-flags/FeatureFlagsProvider.cjs.map +1 -1
- package/dist/forms/FormField.cjs.map +1 -1
- package/dist/forms/masked-inputs.cjs +1 -1
- package/dist/forms/masked-inputs.cjs.map +1 -1
- package/dist/forms/masked-inputs.js +1 -1
- package/dist/forms/masked-inputs.js.map +1 -1
- package/dist/forms/use-viacep.cjs.map +1 -1
- package/dist/forms/use-zod-form.cjs.map +1 -1
- package/dist/forms/zod-resolver.cjs.map +1 -1
- package/dist/forms/zod-resolver.js.map +1 -1
- package/dist/geo/TrajectoryMap.cjs.map +1 -1
- package/dist/geo/TrajectoryMap.js.map +1 -1
- package/dist/geo/leaflet-map.cjs.map +1 -1
- package/dist/geo/leaflet-map.js.map +1 -1
- package/dist/geo/use-position-tracker.cjs.map +1 -1
- package/dist/hooks/use-announce.cjs.map +1 -1
- package/dist/hooks/use-async.cjs.map +1 -1
- package/dist/hooks/use-before-install-prompt.cjs.map +1 -1
- package/dist/hooks/use-breakpoint.cjs.map +1 -1
- package/dist/hooks/use-click-outside.cjs.map +1 -1
- package/dist/hooks/use-client-filter.cjs.map +1 -1
- package/dist/hooks/use-clipboard.cjs.map +1 -1
- package/dist/hooks/use-counter.cjs.map +1 -1
- package/dist/hooks/use-debounce.cjs.map +1 -1
- package/dist/hooks/use-deep-memo.cjs.map +1 -1
- package/dist/hooks/use-disclosure.cjs.map +1 -1
- package/dist/hooks/use-document-visibility.cjs.map +1 -1
- package/dist/hooks/use-event-listener.cjs.map +1 -1
- package/dist/hooks/use-geolocation.cjs.map +1 -1
- package/dist/hooks/use-hover.cjs.map +1 -1
- package/dist/hooks/use-idle.cjs.map +1 -1
- package/dist/hooks/use-install-prompt.cjs +1 -1
- package/dist/hooks/use-install-prompt.cjs.map +1 -1
- package/dist/hooks/use-install-prompt.js +1 -1
- package/dist/hooks/use-install-prompt.js.map +1 -1
- package/dist/hooks/use-intersection-observer.cjs.map +1 -1
- package/dist/hooks/use-interval.cjs.map +1 -1
- package/dist/hooks/use-is-first-render.cjs.map +1 -1
- package/dist/hooks/use-keyboard-shortcut.cjs +1 -1
- package/dist/hooks/use-keyboard-shortcut.cjs.map +1 -1
- package/dist/hooks/use-keyboard-shortcut.js +1 -1
- package/dist/hooks/use-keyboard-shortcut.js.map +1 -1
- package/dist/hooks/use-list-state.cjs.map +1 -1
- package/dist/hooks/use-local-storage.cjs.map +1 -1
- package/dist/hooks/use-long-press-handlers.cjs.map +1 -1
- package/dist/hooks/use-long-press.cjs.map +1 -1
- package/dist/hooks/use-map.cjs.map +1 -1
- package/dist/hooks/use-media-devices.cjs.map +1 -1
- package/dist/hooks/use-media-permission.cjs.map +1 -1
- package/dist/hooks/use-media-query.cjs.map +1 -1
- package/dist/hooks/use-object-url.cjs.map +1 -1
- package/dist/hooks/use-online.cjs.map +1 -1
- package/dist/hooks/use-pagination.cjs.map +1 -1
- package/dist/hooks/use-previous.cjs.map +1 -1
- package/dist/hooks/use-queue.cjs.map +1 -1
- package/dist/hooks/use-resize-observer.cjs.map +1 -1
- package/dist/hooks/use-scroll-overflow.cjs.map +1 -1
- package/dist/hooks/use-service-worker-update.cjs.map +1 -1
- package/dist/hooks/use-set.cjs.map +1 -1
- package/dist/hooks/use-sortable.cjs +1 -1
- package/dist/hooks/use-sortable.cjs.map +1 -1
- package/dist/hooks/use-sortable.js +0 -2
- package/dist/hooks/use-sortable.js.map +1 -1
- package/dist/hooks/use-stable-callback.cjs.map +1 -1
- package/dist/hooks/use-storage-estimate.cjs.map +1 -1
- package/dist/hooks/use-throttle.cjs.map +1 -1
- package/dist/hooks/use-timeout.cjs.map +1 -1
- package/dist/hooks/use-toggle.cjs.map +1 -1
- package/dist/hooks/use-window-size.cjs.map +1 -1
- package/dist/http/api-client.cjs +1 -1
- package/dist/http/api-client.cjs.map +1 -1
- package/dist/http/api-client.js +7 -5
- package/dist/http/api-client.js.map +1 -1
- package/dist/http/resumable-upload.cjs +1 -1
- package/dist/http/resumable-upload.cjs.map +1 -1
- package/dist/http/resumable-upload.js +1 -1
- package/dist/http/resumable-upload.js.map +1 -1
- package/dist/http/use-poll.cjs.map +1 -1
- package/dist/i18n/I18nProvider.cjs.map +1 -1
- package/dist/icons/Icon.cjs.map +1 -1
- package/dist/icons/IconProvider.cjs.map +1 -1
- package/dist/icons/icon-context.cjs.map +1 -1
- package/dist/icons/use-icon.cjs.map +1 -1
- package/dist/imaging/use-image-processing.cjs.map +1 -1
- package/dist/oauth/GoogleSignIn.cjs.map +1 -1
- package/dist/oauth/GoogleSignIn.js.map +1 -1
- package/dist/oauth/use-oauth-callback.cjs.map +1 -1
- package/dist/offline/use-offline-sync.cjs.map +1 -1
- package/dist/push/use-push-subscription.cjs.map +1 -1
- package/dist/query/QueryProvider.cjs.map +1 -1
- package/dist/query/constants.cjs +1 -1
- package/dist/query/constants.cjs.map +1 -1
- package/dist/query/constants.js +10 -10
- package/dist/query/constants.js.map +1 -1
- package/dist/query/create-query-keys.cjs +1 -1
- package/dist/query/create-query-keys.cjs.map +1 -1
- package/dist/query/create-query-keys.js +1 -1
- package/dist/query/create-query-keys.js.map +1 -1
- package/dist/query/offline-persistence.cjs.map +1 -1
- package/dist/query/use-cursor-query.cjs.map +1 -1
- package/dist/query/use-offline-mutation.cjs.map +1 -1
- package/dist/query/use-paginated-query.cjs.map +1 -1
- package/dist/router/AppRouter.cjs +1 -1
- package/dist/router/AppRouter.cjs.map +1 -1
- package/dist/router/AppRouter.js +7 -2
- package/dist/router/AppRouter.js.map +1 -1
- package/dist/sse/use-event-stream.cjs.map +1 -1
- package/dist/store/create-store.cjs.map +1 -1
- package/dist/tabular/use-tabular-predictor.cjs.map +1 -1
- package/dist/telemetry/TelemetryProvider.cjs.map +1 -1
- package/dist/tempest-react-sdk.d.ts +25 -26
- package/dist/theme/ThemeProvider.cjs.map +1 -1
- package/dist/theme/color.cjs +1 -1
- package/dist/theme/color.cjs.map +1 -1
- package/dist/theme/color.js +1 -1
- package/dist/theme/color.js.map +1 -1
- package/dist/utils/cn.cjs +1 -1
- package/dist/utils/cn.cjs.map +1 -1
- package/dist/utils/cn.js +1 -1
- package/dist/utils/cn.js.map +1 -1
- package/dist/utils/objects.cjs +1 -1
- package/dist/utils/objects.cjs.map +1 -1
- package/dist/utils/objects.js +1 -1
- package/dist/utils/objects.js.map +1 -1
- package/dist/utils/xlsx.cjs +1 -1
- package/dist/utils/xlsx.cjs.map +1 -1
- package/dist/utils/xlsx.js +3 -3
- package/dist/utils/xlsx.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/metadata.cjs +1 -1
- package/dist/vision/core/metadata.cjs.map +1 -1
- package/dist/vision/core/metadata.js +1 -1
- package/dist/vision/core/metadata.js.map +1 -1
- package/dist/vision/io/image.cjs +1 -1
- package/dist/vision/io/image.cjs.map +1 -1
- package/dist/vision/io/image.js +4 -2
- package/dist/vision/io/image.js.map +1 -1
- package/dist/vision/preprocess/image.cjs +1 -1
- package/dist/vision/preprocess/image.cjs.map +1 -1
- package/dist/vision/preprocess/image.js +4 -2
- package/dist/vision/preprocess/image.js.map +1 -1
- package/dist/vision/tasks/base.cjs +1 -1
- package/dist/vision/tasks/base.cjs.map +1 -1
- package/dist/vision/tasks/base.js +3 -1
- package/dist/vision/tasks/base.js.map +1 -1
- package/dist/vision/tasks/classifier.cjs +1 -1
- package/dist/vision/tasks/classifier.cjs.map +1 -1
- package/dist/vision/tasks/classifier.js +2 -2
- package/dist/vision/tasks/classifier.js.map +1 -1
- package/dist/vision/tasks/detectClassify.cjs +1 -1
- package/dist/vision/tasks/detectClassify.cjs.map +1 -1
- package/dist/vision/tasks/detectClassify.js +16 -15
- package/dist/vision/tasks/detectClassify.js.map +1 -1
- package/dist/vision/use-camera-stream.cjs.map +1 -1
- package/dist/vision/use-live-luminance.cjs.map +1 -1
- package/dist/vite/create-vite-config.cjs +1 -1
- package/dist/vite/create-vite-config.cjs.map +1 -1
- package/dist/vite/create-vite-config.js +3 -3
- package/dist/vite/create-vite-config.js.map +1 -1
- package/dist/vite/tempest-icons.cjs.map +1 -1
- package/dist/vite/tempest-pwa-dev-sw.cjs.map +1 -1
- package/dist/vite/tempest-pwa-icons.cjs.map +1 -1
- package/dist/ws/use-web-socket.cjs +1 -1
- package/dist/ws/use-web-socket.cjs.map +1 -1
- package/dist/ws/use-web-socket.js +5 -3
- package/dist/ws/use-web-socket.js.map +1 -1
- package/package.json +2 -2
- package/template/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AIChatComposer.cjs","names":[],"sources":["../../../src/components/AIChat/AIChatComposer.tsx"],"sourcesContent":["/**\n * @tempest-limits props-count, function-lines — a textarea that grows, submits on\n * Enter, offers a stop button while a turn is generating and clears only when onSend\n * resolves. The eight props are that contract (onSend, onStop, generating, onError)\n * plus its slots (actions, footer) and shape (maxRows, locale); the body is the\n * autosize measurement and the key handling, which both need the same ref and cannot\n * be lifted out of it.\n */\nimport {\n forwardRef,\n useImperativeHandle,\n useLayoutEffect,\n useRef,\n useState,\n type FormEvent,\n type KeyboardEvent,\n type ReactNode,\n type TextareaHTMLAttributes,\n} from \"react\";\n\nimport { cn } from \"@/utils/cn\";\n\nimport { aiChatStrings } from \"./ai-chat-turns\";\nimport styles from \"./AIChat.module.css\";\n\n/** DOM attributes the composer redefines. */\ntype OverriddenDomProps = \"onSubmit\" | \"value\" | \"defaultValue\" | \"rows\";\n\nexport interface AIChatComposerProps extends Omit<\n TextareaHTMLAttributes<HTMLTextAreaElement>,\n OverriddenDomProps\n> {\n /** Called with the trimmed prompt. The field clears only when this does not throw. */\n onSend: (text: string) => void | Promise<void>;\n /**\n * Abort the turn in flight.\n *\n * When given together with `generating`, the send button becomes a stop button\n * and `Escape` aborts too.\n */\n onStop?: () => void;\n /** A turn is being generated. Replaces send with stop and refuses to send. */\n generating?: boolean;\n /** Locale for the placeholder and the button labels. Default `\"pt-BR\"`. */\n locale?: \"pt-BR\" | \"en\";\n /** Left of the send button — an attach control, a model picker, a tool toggle. */\n actions?: ReactNode;\n /** Under the field — a token count, the model name, a disclaimer. */\n footer?: ReactNode;\n /** Largest height the field grows to, in lines. Default 8. */\n maxRows?: number;\n /**\n * Called when `onSend` rejects. The draft is kept either way.\n *\n * Without it the rejection is swallowed after the draft is preserved: re-throwing\n * out of a DOM event handler surfaces as an unhandled promise rejection, which is\n * console noise for the developer and nothing the user can act on. The visible\n * signal is the prompt still sitting in the field; wire this to a toast to say why.\n */\n onError?: (error: unknown) => void;\n}\n\n/** Imperative handle, so a thread can focus, read or refill the field. */\nexport interface AIChatComposerHandle {\n focus: () => void;\n /** Replace the draft — used to put a prompt back in the field. */\n setValue: (text: string) => void;\n /**\n * The current draft.\n *\n * The counterpart `setValue` needs to be usable for anything **additive**. The\n * field is uncontrolled, so without this the only way to append to a draft — a\n * dictated phrase, a picked slash-command, a pasted citation — is to shadow the\n * whole value in app state through `onChange` and hope the two never drift.\n */\n getValue: () => string;\n}\n\n/**\n * The prompt field of a conversation with a model: a textarea that grows with its\n * content, sends on `Enter`, keeps `Shift+Enter` for a newline, and turns into a\n * stop button while a turn is streaming.\n *\n * Uncontrolled on purpose. A draft changes on every keystroke, and lifting that into\n * app state re-renders the whole transcript per character — with a streaming answer\n * above, that is the one place where \"controlled by default\" costs something\n * visible. Apps that need the draft (a persisted composer, a slash-command menu)\n * read it from `onChange` or drive it through the ref.\n *\n * @example\n * <AIChatComposer\n * generating={generating}\n * onSend={(text) => ask(text)}\n * onStop={() => controller.abort()}\n * footer={<small>Claude Opus 5 · pode errar</small>}\n * />\n */\nexport const AIChatComposer = forwardRef<AIChatComposerHandle, AIChatComposerProps>(\n function AIChatComposer(\n {\n onSend,\n onStop,\n generating = false,\n locale = \"pt-BR\",\n actions,\n footer,\n maxRows = 8,\n onError,\n className,\n disabled,\n placeholder,\n onKeyDown,\n onChange,\n ...rest\n },\n ref,\n ) {\n const strings = aiChatStrings(locale);\n const textarea = useRef<HTMLTextAreaElement | null>(null);\n const [value, setValue] = useState(\"\");\n const [busy, setBusy] = useState(false);\n\n useImperativeHandle(ref, () => ({\n focus: () => textarea.current?.focus(),\n setValue: (text: string) => {\n setValue(text);\n textarea.current?.focus();\n },\n getValue: () => value,\n }));\n\n /**\n * Grow the field to fit its content, up to `maxRows`.\n *\n * Measured from `scrollHeight` after resetting the height, because\n * `scrollHeight` on an element that is already tall enough reports the current\n * height and the field would never shrink back.\n */\n useLayoutEffect(() => {\n const node = textarea.current;\n if (!node) return;\n node.style.height = \"auto\";\n const lineHeight = Number.parseFloat(getComputedStyle(node).lineHeight) || 20;\n const max = lineHeight * maxRows;\n node.style.height = `${Math.min(node.scrollHeight, max)}px`;\n node.style.overflowY = node.scrollHeight > max ? \"auto\" : \"hidden\";\n }, [value, maxRows]);\n\n const submit = async (): Promise<void> => {\n const text = value.trim();\n if (!text || busy || disabled || generating) return;\n setBusy(true);\n try {\n await onSend(text);\n setValue(\"\");\n } catch (error) {\n onError?.(error);\n } finally {\n setBusy(false);\n }\n };\n\n const handleSubmit = (event: FormEvent): void => {\n event.preventDefault();\n void submit();\n };\n\n /**\n * `Enter` sends, `Shift+Enter` breaks the line, `Escape` aborts a turn in\n * flight.\n *\n * The IME check is not optional: while composing Japanese or Korean, `Enter`\n * confirms the candidate word and `keyCode === 229` marks that keystroke.\n * Sending there would post half a word and eat the confirmation.\n */\n const handleKeyDown = (event: KeyboardEvent<HTMLTextAreaElement>): void => {\n onKeyDown?.(event);\n if (event.defaultPrevented) return;\n if (event.key === \"Escape\" && generating && onStop) {\n event.preventDefault();\n onStop();\n return;\n }\n if (event.key !== \"Enter\" || event.shiftKey) return;\n if (event.nativeEvent.isComposing || event.keyCode === 229) return;\n event.preventDefault();\n void submit();\n };\n\n const showStop = generating && onStop !== undefined;\n\n return (\n <form className={cn(styles.composer, className)} onSubmit={handleSubmit}>\n <div className={styles.composerBox}>\n <textarea\n {...rest}\n ref={textarea}\n className={styles.field}\n rows={1}\n value={value}\n disabled={disabled}\n placeholder={placeholder ?? strings.placeholder}\n onChange={(event) => {\n setValue(event.target.value);\n onChange?.(event);\n }}\n onKeyDown={handleKeyDown}\n />\n <div className={styles.composerActions}>\n {actions}\n {showStop ? (\n <button type=\"button\" className={styles.stop} onClick={onStop}>\n <span aria-hidden=\"true\" className={styles.stopGlyph} />\n {strings.stop}\n </button>\n ) : (\n <button\n type=\"submit\"\n className={styles.send}\n disabled={disabled || busy || generating || value.trim() === \"\"}\n >\n {strings.send}\n </button>\n )}\n </div>\n </div>\n {footer && <div className={styles.composerFooter}>{footer}</div>}\n </form>\n );\n },\n);\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"AIChatComposer.cjs","names":[],"sources":["../../../src/components/AIChat/AIChatComposer.tsx"],"sourcesContent":["/**\n * @tempest-limits props-count, function-lines — a textarea that grows, submits on\n * Enter, offers a stop button while a turn is generating and clears only when onSend\n * resolves. The eight props are that contract (onSend, onStop, generating, onError)\n * plus its slots (actions, footer) and shape (maxRows, locale); the body is the\n * autosize measurement and the key handling, which both need the same ref and cannot\n * be lifted out of it.\n */\nimport {\n forwardRef,\n useImperativeHandle,\n useLayoutEffect,\n useRef,\n useState,\n type FormEvent,\n type KeyboardEvent,\n type ReactNode,\n type TextareaHTMLAttributes,\n} from \"react\";\n\nimport { cn } from \"@/utils/cn\";\n\nimport { aiChatStrings } from \"./ai-chat-turns\";\nimport styles from \"./AIChat.module.css\";\n\n/** DOM attributes the composer redefines. */\ntype OverriddenDomProps = \"onSubmit\" | \"value\" | \"defaultValue\" | \"rows\";\n\nexport interface AIChatComposerProps extends Omit<\n TextareaHTMLAttributes<HTMLTextAreaElement>,\n OverriddenDomProps\n> {\n /** Called with the trimmed prompt. The field clears only when this does not throw. */\n onSend: (text: string) => void | Promise<void>;\n /**\n * Abort the turn in flight.\n *\n * When given together with `generating`, the send button becomes a stop button\n * and `Escape` aborts too.\n */\n onStop?: () => void;\n /** A turn is being generated. Replaces send with stop and refuses to send. */\n generating?: boolean;\n /** Locale for the placeholder and the button labels. Default `\"pt-BR\"`. */\n locale?: \"pt-BR\" | \"en\";\n /** Left of the send button — an attach control, a model picker, a tool toggle. */\n actions?: ReactNode;\n /** Under the field — a token count, the model name, a disclaimer. */\n footer?: ReactNode;\n /** Largest height the field grows to, in lines. Default 8. */\n maxRows?: number;\n /**\n * Called when `onSend` rejects. The draft is kept either way.\n *\n * Without it the rejection is swallowed after the draft is preserved: re-throwing\n * out of a DOM event handler surfaces as an unhandled promise rejection, which is\n * console noise for the developer and nothing the user can act on. The visible\n * signal is the prompt still sitting in the field; wire this to a toast to say why.\n */\n onError?: (error: unknown) => void;\n}\n\n/** Imperative handle, so a thread can focus, read or refill the field. */\nexport interface AIChatComposerHandle {\n focus: () => void;\n /** Replace the draft — used to put a prompt back in the field. */\n setValue: (text: string) => void;\n /**\n * The current draft.\n *\n * The counterpart `setValue` needs to be usable for anything **additive**. The\n * field is uncontrolled, so without this the only way to append to a draft — a\n * dictated phrase, a picked slash-command, a pasted citation — is to shadow the\n * whole value in app state through `onChange` and hope the two never drift.\n */\n getValue: () => string;\n}\n\n/**\n * The prompt field of a conversation with a model: a textarea that grows with its\n * content, sends on `Enter`, keeps `Shift+Enter` for a newline, and turns into a\n * stop button while a turn is streaming.\n *\n * Uncontrolled on purpose. A draft changes on every keystroke, and lifting that into\n * app state re-renders the whole transcript per character — with a streaming answer\n * above, that is the one place where \"controlled by default\" costs something\n * visible. Apps that need the draft (a persisted composer, a slash-command menu)\n * read it from `onChange` or drive it through the ref.\n *\n * @example\n * <AIChatComposer\n * generating={generating}\n * onSend={(text) => ask(text)}\n * onStop={() => controller.abort()}\n * footer={<small>Claude Opus 5 · pode errar</small>}\n * />\n */\nexport const AIChatComposer = forwardRef<AIChatComposerHandle, AIChatComposerProps>(\n function AIChatComposer(\n {\n onSend,\n onStop,\n generating = false,\n locale = \"pt-BR\",\n actions,\n footer,\n maxRows = 8,\n onError,\n className,\n disabled,\n placeholder,\n onKeyDown,\n onChange,\n ...rest\n },\n ref,\n ) {\n const strings = aiChatStrings(locale);\n const textarea = useRef<HTMLTextAreaElement | null>(null);\n const [value, setValue] = useState(\"\");\n const [busy, setBusy] = useState(false);\n\n useImperativeHandle(ref, () => ({\n focus: () => textarea.current?.focus(),\n setValue: (text: string) => {\n setValue(text);\n textarea.current?.focus();\n },\n getValue: () => value,\n }));\n\n /**\n * Grow the field to fit its content, up to `maxRows`.\n *\n * Measured from `scrollHeight` after resetting the height, because\n * `scrollHeight` on an element that is already tall enough reports the current\n * height and the field would never shrink back.\n */\n useLayoutEffect(() => {\n const node = textarea.current;\n if (!node) return;\n node.style.height = \"auto\";\n const lineHeight = Number.parseFloat(getComputedStyle(node).lineHeight) || 20;\n const max = lineHeight * maxRows;\n node.style.height = `${Math.min(node.scrollHeight, max)}px`;\n node.style.overflowY = node.scrollHeight > max ? \"auto\" : \"hidden\";\n }, [value, maxRows]);\n\n const submit = async (): Promise<void> => {\n const text = value.trim();\n if (!text || busy || disabled || generating) return;\n setBusy(true);\n try {\n await onSend(text);\n setValue(\"\");\n } catch (error) {\n onError?.(error);\n } finally {\n setBusy(false);\n }\n };\n\n const handleSubmit = (event: FormEvent): void => {\n event.preventDefault();\n void submit();\n };\n\n /**\n * `Enter` sends, `Shift+Enter` breaks the line, `Escape` aborts a turn in\n * flight.\n *\n * The IME check is not optional: while composing Japanese or Korean, `Enter`\n * confirms the candidate word and `keyCode === 229` marks that keystroke.\n * Sending there would post half a word and eat the confirmation.\n */\n const handleKeyDown = (event: KeyboardEvent<HTMLTextAreaElement>): void => {\n onKeyDown?.(event);\n if (event.defaultPrevented) return;\n if (event.key === \"Escape\" && generating && onStop) {\n event.preventDefault();\n onStop();\n return;\n }\n if (event.key !== \"Enter\" || event.shiftKey) return;\n if (event.nativeEvent.isComposing || event.keyCode === 229) return;\n event.preventDefault();\n void submit();\n };\n\n const showStop = generating && onStop !== undefined;\n\n return (\n <form className={cn(styles.composer, className)} onSubmit={handleSubmit}>\n <div className={styles.composerBox}>\n <textarea\n {...rest}\n ref={textarea}\n className={styles.field}\n rows={1}\n value={value}\n disabled={disabled}\n placeholder={placeholder ?? strings.placeholder}\n onChange={(event) => {\n setValue(event.target.value);\n onChange?.(event);\n }}\n onKeyDown={handleKeyDown}\n />\n <div className={styles.composerActions}>\n {actions}\n {showStop ? (\n <button type=\"button\" className={styles.stop} onClick={onStop}>\n <span aria-hidden=\"true\" className={styles.stopGlyph} />\n {strings.stop}\n </button>\n ) : (\n <button\n type=\"submit\"\n className={styles.send}\n disabled={disabled || busy || generating || value.trim() === \"\"}\n >\n {strings.send}\n </button>\n )}\n </div>\n </div>\n {footer && <div className={styles.composerFooter}>{footer}</div>}\n </form>\n );\n },\n);\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AIChatComposer.js","names":[],"sources":["../../../src/components/AIChat/AIChatComposer.tsx"],"sourcesContent":["/**\n * @tempest-limits props-count, function-lines — a textarea that grows, submits on\n * Enter, offers a stop button while a turn is generating and clears only when onSend\n * resolves. The eight props are that contract (onSend, onStop, generating, onError)\n * plus its slots (actions, footer) and shape (maxRows, locale); the body is the\n * autosize measurement and the key handling, which both need the same ref and cannot\n * be lifted out of it.\n */\nimport {\n forwardRef,\n useImperativeHandle,\n useLayoutEffect,\n useRef,\n useState,\n type FormEvent,\n type KeyboardEvent,\n type ReactNode,\n type TextareaHTMLAttributes,\n} from \"react\";\n\nimport { cn } from \"@/utils/cn\";\n\nimport { aiChatStrings } from \"./ai-chat-turns\";\nimport styles from \"./AIChat.module.css\";\n\n/** DOM attributes the composer redefines. */\ntype OverriddenDomProps = \"onSubmit\" | \"value\" | \"defaultValue\" | \"rows\";\n\nexport interface AIChatComposerProps extends Omit<\n TextareaHTMLAttributes<HTMLTextAreaElement>,\n OverriddenDomProps\n> {\n /** Called with the trimmed prompt. The field clears only when this does not throw. */\n onSend: (text: string) => void | Promise<void>;\n /**\n * Abort the turn in flight.\n *\n * When given together with `generating`, the send button becomes a stop button\n * and `Escape` aborts too.\n */\n onStop?: () => void;\n /** A turn is being generated. Replaces send with stop and refuses to send. */\n generating?: boolean;\n /** Locale for the placeholder and the button labels. Default `\"pt-BR\"`. */\n locale?: \"pt-BR\" | \"en\";\n /** Left of the send button — an attach control, a model picker, a tool toggle. */\n actions?: ReactNode;\n /** Under the field — a token count, the model name, a disclaimer. */\n footer?: ReactNode;\n /** Largest height the field grows to, in lines. Default 8. */\n maxRows?: number;\n /**\n * Called when `onSend` rejects. The draft is kept either way.\n *\n * Without it the rejection is swallowed after the draft is preserved: re-throwing\n * out of a DOM event handler surfaces as an unhandled promise rejection, which is\n * console noise for the developer and nothing the user can act on. The visible\n * signal is the prompt still sitting in the field; wire this to a toast to say why.\n */\n onError?: (error: unknown) => void;\n}\n\n/** Imperative handle, so a thread can focus, read or refill the field. */\nexport interface AIChatComposerHandle {\n focus: () => void;\n /** Replace the draft — used to put a prompt back in the field. */\n setValue: (text: string) => void;\n /**\n * The current draft.\n *\n * The counterpart `setValue` needs to be usable for anything **additive**. The\n * field is uncontrolled, so without this the only way to append to a draft — a\n * dictated phrase, a picked slash-command, a pasted citation — is to shadow the\n * whole value in app state through `onChange` and hope the two never drift.\n */\n getValue: () => string;\n}\n\n/**\n * The prompt field of a conversation with a model: a textarea that grows with its\n * content, sends on `Enter`, keeps `Shift+Enter` for a newline, and turns into a\n * stop button while a turn is streaming.\n *\n * Uncontrolled on purpose. A draft changes on every keystroke, and lifting that into\n * app state re-renders the whole transcript per character — with a streaming answer\n * above, that is the one place where \"controlled by default\" costs something\n * visible. Apps that need the draft (a persisted composer, a slash-command menu)\n * read it from `onChange` or drive it through the ref.\n *\n * @example\n * <AIChatComposer\n * generating={generating}\n * onSend={(text) => ask(text)}\n * onStop={() => controller.abort()}\n * footer={<small>Claude Opus 5 · pode errar</small>}\n * />\n */\nexport const AIChatComposer = forwardRef<AIChatComposerHandle, AIChatComposerProps>(\n function AIChatComposer(\n {\n onSend,\n onStop,\n generating = false,\n locale = \"pt-BR\",\n actions,\n footer,\n maxRows = 8,\n onError,\n className,\n disabled,\n placeholder,\n onKeyDown,\n onChange,\n ...rest\n },\n ref,\n ) {\n const strings = aiChatStrings(locale);\n const textarea = useRef<HTMLTextAreaElement | null>(null);\n const [value, setValue] = useState(\"\");\n const [busy, setBusy] = useState(false);\n\n useImperativeHandle(ref, () => ({\n focus: () => textarea.current?.focus(),\n setValue: (text: string) => {\n setValue(text);\n textarea.current?.focus();\n },\n getValue: () => value,\n }));\n\n /**\n * Grow the field to fit its content, up to `maxRows`.\n *\n * Measured from `scrollHeight` after resetting the height, because\n * `scrollHeight` on an element that is already tall enough reports the current\n * height and the field would never shrink back.\n */\n useLayoutEffect(() => {\n const node = textarea.current;\n if (!node) return;\n node.style.height = \"auto\";\n const lineHeight = Number.parseFloat(getComputedStyle(node).lineHeight) || 20;\n const max = lineHeight * maxRows;\n node.style.height = `${Math.min(node.scrollHeight, max)}px`;\n node.style.overflowY = node.scrollHeight > max ? \"auto\" : \"hidden\";\n }, [value, maxRows]);\n\n const submit = async (): Promise<void> => {\n const text = value.trim();\n if (!text || busy || disabled || generating) return;\n setBusy(true);\n try {\n await onSend(text);\n setValue(\"\");\n } catch (error) {\n onError?.(error);\n } finally {\n setBusy(false);\n }\n };\n\n const handleSubmit = (event: FormEvent): void => {\n event.preventDefault();\n void submit();\n };\n\n /**\n * `Enter` sends, `Shift+Enter` breaks the line, `Escape` aborts a turn in\n * flight.\n *\n * The IME check is not optional: while composing Japanese or Korean, `Enter`\n * confirms the candidate word and `keyCode === 229` marks that keystroke.\n * Sending there would post half a word and eat the confirmation.\n */\n const handleKeyDown = (event: KeyboardEvent<HTMLTextAreaElement>): void => {\n onKeyDown?.(event);\n if (event.defaultPrevented) return;\n if (event.key === \"Escape\" && generating && onStop) {\n event.preventDefault();\n onStop();\n return;\n }\n if (event.key !== \"Enter\" || event.shiftKey) return;\n if (event.nativeEvent.isComposing || event.keyCode === 229) return;\n event.preventDefault();\n void submit();\n };\n\n const showStop = generating && onStop !== undefined;\n\n return (\n <form className={cn(styles.composer, className)} onSubmit={handleSubmit}>\n <div className={styles.composerBox}>\n <textarea\n {...rest}\n ref={textarea}\n className={styles.field}\n rows={1}\n value={value}\n disabled={disabled}\n placeholder={placeholder ?? strings.placeholder}\n onChange={(event) => {\n setValue(event.target.value);\n onChange?.(event);\n }}\n onKeyDown={handleKeyDown}\n />\n <div className={styles.composerActions}>\n {actions}\n {showStop ? (\n <button type=\"button\" className={styles.stop} onClick={onStop}>\n <span aria-hidden=\"true\" className={styles.stopGlyph} />\n {strings.stop}\n </button>\n ) : (\n <button\n type=\"submit\"\n className={styles.send}\n disabled={disabled || busy || generating || value.trim() === \"\"}\n >\n {strings.send}\n </button>\n )}\n </div>\n </div>\n {footer && <div className={styles.composerFooter}>{footer}</div>}\n </form>\n );\n },\n);\n"],"mappings":";;;;;;AAiGA,IAAa,IAAiB,EAC1B,SACI,EACI,WACA,WACA,gBAAa,IACb,YAAS,SACT,YACA,WACA,aAAU,GACV,YACA,cACA,aACA,gBACA,cACA,aACA,GAAG,KAEP,GACF;CACE,IAAM,IAAU,EAAc,CAAM,GAC9B,IAAW,EAAmC,IAAI,GAClD,CAAC,GAAO,KAAY,EAAS,EAAE,GAC/B,CAAC,GAAM,KAAW,EAAS,EAAK;CAkBtC,AAhBA,EAAoB,UAAY;EAC5B,aAAa,EAAS,SAAS,MAAM;EACrC,WAAW,MAAiB;GAExB,AADA,EAAS,CAAI,GACb,EAAS,SAAS,MAAM;EAC5B;EACA,gBAAgB;CACpB,EAAE,GASF,QAAsB;EAClB,IAAM,IAAO,EAAS;EACtB,IAAI,CAAC,GAAM;EACX,EAAK,MAAM,SAAS;EAEpB,IAAM,KADa,OAAO,WAAW,iBAAiB,CAAI,CAAC,CAAC,UAAU,KAAK,MAClD;EAEzB,AADA,EAAK,MAAM,SAAS,GAAG,KAAK,IAAI,EAAK,cAAc,CAAG,EAAE,KACxD,EAAK,MAAM,YAAY,EAAK,eAAe,IAAM,SAAS;CAC9D,GAAG,CAAC,GAAO,CAAO,CAAC;CAEnB,IAAM,IAAS,YAA2B;EACtC,IAAM,IAAO,EAAM,KAAK;EACpB,OAAC,KAAQ,KAAQ,KAAY,IACjC;KAAQ,EAAI;GACZ,IAAI;IAEA,AADA,MAAM,EAAO,CAAI,GACjB,EAAS,EAAE;GACf,SAAS,GAAO;IACZ,IAAU,CAAK;GACnB,UAAU;IACN,EAAQ,EAAK;GACjB;EARY;CAShB,GAEM,KAAgB,MAA2B;EAE7C,AADA,EAAM,eAAe,GACrB,EAAY;CAChB,GAUM,KAAiB,MAAoD;EACvE,QAAY,CAAK,GACb,GAAM,kBACV;OAAI,EAAM,QAAQ,YAAY,KAAc,GAAQ;IAEhD,AADA,EAAM,eAAe,GACrB,EAAO;IACP;GACJ;GACI,EAAM,QAAQ,WAAW,EAAM,YAC/B,EAAM,YAAY,eAAe,EAAM,YAAY,QACvD,EAAM,eAAe,GACrB,EAAY;EAJZ;CAKJ,GAEM,IAAW,KAAc,MAAW,KAAA;CAE1C,OACI,kBAAC,QAAD;EAAM,WAAW,EAAG,EAAO,UAAU,CAAS;EAAG,UAAU;YAA3D,CACI,kBAAC,OAAD;GAAK,WAAW,EAAO;aAAvB,CACI,kBAAC,YAAD;IACI,GAAI;IACJ,KAAK;IACL,WAAW,EAAO;IAClB,MAAM;IACC;IACG;IACV,aAAa,KAAe,EAAQ;IACpC,WAAW,MAAU;KAEjB,AADA,EAAS,EAAM,OAAO,KAAK,GAC3B,IAAW,CAAK;IACpB;IACA,WAAW;GACd,CAAA,GACD,kBAAC,OAAD;IAAK,WAAW,EAAO;cAAvB,CACK,GACA,IACG,kBAAC,UAAD;KAAQ,MAAK;KAAS,WAAW,EAAO;KAAM,SAAS;eAAvD,CACI,kBAAC,QAAD;MAAM,eAAY;MAAO,WAAW,EAAO;KAAY,CAAA,GACtD,EAAQ,IACL;SAER,kBAAC,UAAD;KACI,MAAK;KACL,WAAW,EAAO;KAClB,UAAU,KAAY,KAAQ,KAAc,EAAM,KAAK,MAAM;eAE5D,EAAQ;IACL,CAAA,CAEX;KACJ;MACJ,KAAU,kBAAC,OAAD;GAAK,WAAW,EAAO;aAAiB;EAAY,CAAA,CAC7D;;AAEd,CACJ"}
|
|
1
|
+
{"version":3,"file":"AIChatComposer.js","names":[],"sources":["../../../src/components/AIChat/AIChatComposer.tsx"],"sourcesContent":["/**\n * @tempest-limits props-count, function-lines — a textarea that grows, submits on\n * Enter, offers a stop button while a turn is generating and clears only when onSend\n * resolves. The eight props are that contract (onSend, onStop, generating, onError)\n * plus its slots (actions, footer) and shape (maxRows, locale); the body is the\n * autosize measurement and the key handling, which both need the same ref and cannot\n * be lifted out of it.\n */\nimport {\n forwardRef,\n useImperativeHandle,\n useLayoutEffect,\n useRef,\n useState,\n type FormEvent,\n type KeyboardEvent,\n type ReactNode,\n type TextareaHTMLAttributes,\n} from \"react\";\n\nimport { cn } from \"@/utils/cn\";\n\nimport { aiChatStrings } from \"./ai-chat-turns\";\nimport styles from \"./AIChat.module.css\";\n\n/** DOM attributes the composer redefines. */\ntype OverriddenDomProps = \"onSubmit\" | \"value\" | \"defaultValue\" | \"rows\";\n\nexport interface AIChatComposerProps extends Omit<\n TextareaHTMLAttributes<HTMLTextAreaElement>,\n OverriddenDomProps\n> {\n /** Called with the trimmed prompt. The field clears only when this does not throw. */\n onSend: (text: string) => void | Promise<void>;\n /**\n * Abort the turn in flight.\n *\n * When given together with `generating`, the send button becomes a stop button\n * and `Escape` aborts too.\n */\n onStop?: () => void;\n /** A turn is being generated. Replaces send with stop and refuses to send. */\n generating?: boolean;\n /** Locale for the placeholder and the button labels. Default `\"pt-BR\"`. */\n locale?: \"pt-BR\" | \"en\";\n /** Left of the send button — an attach control, a model picker, a tool toggle. */\n actions?: ReactNode;\n /** Under the field — a token count, the model name, a disclaimer. */\n footer?: ReactNode;\n /** Largest height the field grows to, in lines. Default 8. */\n maxRows?: number;\n /**\n * Called when `onSend` rejects. The draft is kept either way.\n *\n * Without it the rejection is swallowed after the draft is preserved: re-throwing\n * out of a DOM event handler surfaces as an unhandled promise rejection, which is\n * console noise for the developer and nothing the user can act on. The visible\n * signal is the prompt still sitting in the field; wire this to a toast to say why.\n */\n onError?: (error: unknown) => void;\n}\n\n/** Imperative handle, so a thread can focus, read or refill the field. */\nexport interface AIChatComposerHandle {\n focus: () => void;\n /** Replace the draft — used to put a prompt back in the field. */\n setValue: (text: string) => void;\n /**\n * The current draft.\n *\n * The counterpart `setValue` needs to be usable for anything **additive**. The\n * field is uncontrolled, so without this the only way to append to a draft — a\n * dictated phrase, a picked slash-command, a pasted citation — is to shadow the\n * whole value in app state through `onChange` and hope the two never drift.\n */\n getValue: () => string;\n}\n\n/**\n * The prompt field of a conversation with a model: a textarea that grows with its\n * content, sends on `Enter`, keeps `Shift+Enter` for a newline, and turns into a\n * stop button while a turn is streaming.\n *\n * Uncontrolled on purpose. A draft changes on every keystroke, and lifting that into\n * app state re-renders the whole transcript per character — with a streaming answer\n * above, that is the one place where \"controlled by default\" costs something\n * visible. Apps that need the draft (a persisted composer, a slash-command menu)\n * read it from `onChange` or drive it through the ref.\n *\n * @example\n * <AIChatComposer\n * generating={generating}\n * onSend={(text) => ask(text)}\n * onStop={() => controller.abort()}\n * footer={<small>Claude Opus 5 · pode errar</small>}\n * />\n */\nexport const AIChatComposer = forwardRef<AIChatComposerHandle, AIChatComposerProps>(\n function AIChatComposer(\n {\n onSend,\n onStop,\n generating = false,\n locale = \"pt-BR\",\n actions,\n footer,\n maxRows = 8,\n onError,\n className,\n disabled,\n placeholder,\n onKeyDown,\n onChange,\n ...rest\n },\n ref,\n ) {\n const strings = aiChatStrings(locale);\n const textarea = useRef<HTMLTextAreaElement | null>(null);\n const [value, setValue] = useState(\"\");\n const [busy, setBusy] = useState(false);\n\n useImperativeHandle(ref, () => ({\n focus: () => textarea.current?.focus(),\n setValue: (text: string) => {\n setValue(text);\n textarea.current?.focus();\n },\n getValue: () => value,\n }));\n\n /**\n * Grow the field to fit its content, up to `maxRows`.\n *\n * Measured from `scrollHeight` after resetting the height, because\n * `scrollHeight` on an element that is already tall enough reports the current\n * height and the field would never shrink back.\n */\n useLayoutEffect(() => {\n const node = textarea.current;\n if (!node) return;\n node.style.height = \"auto\";\n const lineHeight = Number.parseFloat(getComputedStyle(node).lineHeight) || 20;\n const max = lineHeight * maxRows;\n node.style.height = `${Math.min(node.scrollHeight, max)}px`;\n node.style.overflowY = node.scrollHeight > max ? \"auto\" : \"hidden\";\n }, [value, maxRows]);\n\n const submit = async (): Promise<void> => {\n const text = value.trim();\n if (!text || busy || disabled || generating) return;\n setBusy(true);\n try {\n await onSend(text);\n setValue(\"\");\n } catch (error) {\n onError?.(error);\n } finally {\n setBusy(false);\n }\n };\n\n const handleSubmit = (event: FormEvent): void => {\n event.preventDefault();\n void submit();\n };\n\n /**\n * `Enter` sends, `Shift+Enter` breaks the line, `Escape` aborts a turn in\n * flight.\n *\n * The IME check is not optional: while composing Japanese or Korean, `Enter`\n * confirms the candidate word and `keyCode === 229` marks that keystroke.\n * Sending there would post half a word and eat the confirmation.\n */\n const handleKeyDown = (event: KeyboardEvent<HTMLTextAreaElement>): void => {\n onKeyDown?.(event);\n if (event.defaultPrevented) return;\n if (event.key === \"Escape\" && generating && onStop) {\n event.preventDefault();\n onStop();\n return;\n }\n if (event.key !== \"Enter\" || event.shiftKey) return;\n if (event.nativeEvent.isComposing || event.keyCode === 229) return;\n event.preventDefault();\n void submit();\n };\n\n const showStop = generating && onStop !== undefined;\n\n return (\n <form className={cn(styles.composer, className)} onSubmit={handleSubmit}>\n <div className={styles.composerBox}>\n <textarea\n {...rest}\n ref={textarea}\n className={styles.field}\n rows={1}\n value={value}\n disabled={disabled}\n placeholder={placeholder ?? strings.placeholder}\n onChange={(event) => {\n setValue(event.target.value);\n onChange?.(event);\n }}\n onKeyDown={handleKeyDown}\n />\n <div className={styles.composerActions}>\n {actions}\n {showStop ? (\n <button type=\"button\" className={styles.stop} onClick={onStop}>\n <span aria-hidden=\"true\" className={styles.stopGlyph} />\n {strings.stop}\n </button>\n ) : (\n <button\n type=\"submit\"\n className={styles.send}\n disabled={disabled || busy || generating || value.trim() === \"\"}\n >\n {strings.send}\n </button>\n )}\n </div>\n </div>\n {footer && <div className={styles.composerFooter}>{footer}</div>}\n </form>\n );\n },\n);\n"],"mappings":";;;;;;AAiGA,IAAa,IAAiB,EAC1B,SACI,EACI,WACA,WACA,gBAAa,IACb,YAAS,SACT,YACA,WACA,aAAU,GACV,YACA,cACA,aACA,gBACA,cACA,aACA,GAAG,KAEP,GACF;CACE,IAAM,IAAU,EAAc,CAAM,GAC9B,IAAW,EAAmC,IAAI,GAClD,CAAC,GAAO,KAAY,EAAS,EAAE,GAC/B,CAAC,GAAM,KAAW,EAAS,EAAK;CAkBtC,AAhBA,EAAoB,UAAY;EAC5B,aAAa,EAAS,SAAS,MAAM;EACrC,WAAW,MAAiB;GAExB,AADA,EAAS,CAAI,GACb,EAAS,SAAS,MAAM;EAC5B;EACA,gBAAgB;CACpB,EAAE,GASF,QAAsB;EAClB,IAAM,IAAO,EAAS;EACtB,IAAI,CAAC,GAAM;EACX,EAAK,MAAM,SAAS;EAEpB,IAAM,KADa,OAAO,WAAW,iBAAiB,CAAI,CAAC,CAAC,UAAU,KAAK,MAClD;EAEzB,AADA,EAAK,MAAM,SAAS,GAAG,KAAK,IAAI,EAAK,cAAc,CAAG,EAAE,KACxD,EAAK,MAAM,YAAY,EAAK,eAAe,IAAM,SAAS;CAC9D,GAAG,CAAC,GAAO,CAAO,CAAC;CAEnB,IAAM,IAAS,YAA2B;EACtC,IAAM,IAAO,EAAM,KAAK;EACpB,OAAC,KAAQ,KAAQ,KAAY,IACjC;KAAQ,EAAI;GACZ,IAAI;IAEA,AADA,MAAM,EAAO,CAAI,GACjB,EAAS,EAAE;GACf,SAAS,GAAO;IACZ,IAAU,CAAK;GACnB,UAAU;IACN,EAAQ,EAAK;GACjB;EARY;CAShB,GAEM,KAAgB,MAA2B;EAE7C,AADA,EAAM,eAAe,GACrB,EAAY;CAChB,GAUM,KAAiB,MAAoD;EACvE,QAAY,CAAK,GACb,GAAM,kBACV;OAAI,EAAM,QAAQ,YAAY,KAAc,GAAQ;IAEhD,AADA,EAAM,eAAe,GACrB,EAAO;IACP;GACJ;GACI,EAAM,QAAQ,WAAW,EAAM,YAC/B,EAAM,YAAY,eAAe,EAAM,YAAY,QACvD,EAAM,eAAe,GACrB,EAAY;EAJZ;CAKJ,GAEM,IAAW,KAAc,MAAW,KAAA;CAE1C,OACI,kBAAC,QAAD;EAAM,WAAW,EAAG,EAAO,UAAU,CAAS;EAAG,UAAU;EAA3D,UAAA,CACI,kBAAC,OAAD;GAAK,WAAW,EAAO;GAAvB,UAAA,CACI,kBAAC,YAAD;IACI,GAAI;IACJ,KAAK;IACL,WAAW,EAAO;IAClB,MAAM;IACC;IACG;IACV,aAAa,KAAe,EAAQ;IACpC,WAAW,MAAU;KAEjB,AADA,EAAS,EAAM,OAAO,KAAK,GAC3B,IAAW,CAAK;IACpB;IACA,WAAW;GACd,CAAA,GACD,kBAAC,OAAD;IAAK,WAAW,EAAO;IAAvB,UAAA,CACK,GACA,IACG,kBAAC,UAAD;KAAQ,MAAK;KAAS,WAAW,EAAO;KAAM,SAAS;KAAvD,UAAA,CACI,kBAAC,QAAD;MAAM,eAAY;MAAO,WAAW,EAAO;KAAY,CAAA,GACtD,EAAQ,IACL;IAER,CAAA,IAAA,kBAAC,UAAD;KACI,MAAK;KACL,WAAW,EAAO;KAClB,UAAU,KAAY,KAAQ,KAAc,EAAM,KAAK,MAAM;KAE5D,UAAA,EAAQ;IACL,CAAA,CAEX;GACJ,CAAA,CAAA;EACJ,CAAA,GAAA,KAAU,kBAAC,OAAD;GAAK,WAAW,EAAO;GAAiB,UAAA;EAAY,CAAA,CAC7D;;AAEd,CACJ"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AIChatTurn.cjs","names":[],"sources":["../../../src/components/AIChat/AIChatTurn.tsx"],"sourcesContent":["/**\n * @tempest-limits file-lines, props-count, function-lines — one turn renders six\n * things that each have their own state: Markdown body, collapsible reasoning,\n * attachments, streaming caret, inline edit form and the action row. The 13 props\n * are the callbacks those actions need plus the render slots, and the body is long\n * because the edit form and the action row share the turn's message and its pending\n * state.\n */\nimport { ChevronRight, Copy, Pencil, RefreshCw, ThumbsDown, ThumbsUp } from \"lucide-react\";\nimport { useMemo, useState, type ReactNode } from \"react\";\n\nimport { cn } from \"@/utils/cn\";\nimport { formatBytes } from \"@/utils/numbers\";\n\nimport { Collapsible } from \"../Collapsible\";\nimport { CopyButton } from \"../CopyButton\";\nimport { Markdown } from \"../Markdown\";\nimport { VisuallyHidden } from \"../VisuallyHidden\";\nimport {\n aiChatStrings,\n roleLabel,\n turnTime,\n type AIChatMessage,\n type AIChatVote,\n} from \"./ai-chat-turns\";\nimport styles from \"./AIChat.module.css\";\n\nexport interface AIChatTurnProps {\n /** The turn to render. */\n message: AIChatMessage;\n /** Locale for the labels. Default `\"pt-BR\"`. */\n locale?: \"pt-BR\" | \"en\";\n /**\n * Offer the regenerate control.\n *\n * Only the newest assistant turn should get it — re-asking an older one throws\n * away every turn after it, which is a different operation and needs its own\n * confirmation.\n */\n canRegenerate?: boolean;\n onRegenerate?: (message: AIChatMessage) => void;\n onFeedback?: (message: AIChatMessage, vote: AIChatVote) => void;\n /** Enables the edit control on a user turn. Receives the edited prompt. */\n onEditSubmit?: (message: AIChatMessage, text: string) => void | Promise<void>;\n /**\n * Called when `onEditSubmit` rejects. The editor stays open with the draft either\n * way.\n *\n * Without it the rejection is swallowed after the draft is preserved: re-throwing\n * out of a click handler surfaces as an unhandled promise rejection, which is\n * console noise for the developer and nothing the user can act on.\n */\n onEditError?: (error: unknown) => void;\n /** Enables the retry control on a turn that carries an `error`. */\n onRetry?: (message: AIChatMessage) => void;\n renderAvatar?: (message: AIChatMessage) => ReactNode;\n /** Render the body yourself — a tool-call card, a chart, a citation list. */\n renderContent?: (message: AIChatMessage) => ReactNode;\n /**\n * Rating to show as pressed.\n *\n * Pass it to keep votes in app state (persisted across a reload); leave it out\n * and the pressed state is kept locally, which is enough for a fire-and-forget\n * `onFeedback`.\n */\n vote?: AIChatVote;\n /** Reasoning blocks start expanded. Default `false`. */\n defaultReasoningOpen?: boolean;\n /** Show line numbers in fenced code. Default `false`. */\n showLineNumbers?: boolean;\n}\n\n/**\n * One turn of a conversation with a model.\n *\n * Exported for apps that build their own transcript layout (a split view, a diff of\n * two answers) but still want the SDK's turn: Markdown body, reasoning block,\n * attachments, streaming caret, error state and the action row.\n *\n * @example\n * <AIChatTurn message={turn} canRegenerate onRegenerate={(m) => reask(m)} />\n */\nexport function AIChatTurn({\n message,\n locale = \"pt-BR\",\n canRegenerate = false,\n onRegenerate,\n onFeedback,\n onEditSubmit,\n onEditError,\n onRetry,\n renderAvatar,\n renderContent,\n vote,\n defaultReasoningOpen = false,\n showLineNumbers = false,\n}: AIChatTurnProps) {\n const strings = aiChatStrings(locale);\n const { role, content, reasoning, streaming, error, model, createdAt, attachments } = message;\n const [localVote, setLocalVote] = useState<AIChatVote | null>(null);\n const [editing, setEditing] = useState(false);\n const [draft, setDraft] = useState(content);\n const [saving, setSaving] = useState(false);\n\n const currentVote = vote ?? localVote;\n const isUser = role === \"user\";\n\n /**\n * Memoised so a streaming answer re-parses **only the turn that is growing**.\n *\n * `Markdown` parses in its own render, and React skips re-rendering a child whose\n * element is referentially identical to the previous one. Holding the element\n * across renders is therefore what keeps a fifty-turn transcript from re-parsing\n * every finished answer on every token of the newest one.\n */\n const markdown = useMemo(\n () => (\n <Markdown\n source={content}\n showLineNumbers={showLineNumbers}\n className={styles.markdownRoot}\n />\n ),\n [content, showLineNumbers],\n );\n\n /**\n * Body of the turn.\n *\n * A user turn is plain text with newlines preserved, an assistant turn is\n * Markdown. A model emits Markdown by contract; a person typing `2 * 3 * 4` did\n * not mean to open an emphasis span.\n */\n const body = ((): ReactNode => {\n if (renderContent) return renderContent(message);\n if (isUser) return <div className={styles.plain}>{content}</div>;\n if (content === \"\") return null;\n return markdown;\n })();\n\n const submitEdit = async (): Promise<void> => {\n const text = draft.trim();\n if (!text || saving || !onEditSubmit) return;\n setSaving(true);\n try {\n await onEditSubmit(message, text);\n setEditing(false);\n } catch (error) {\n onEditError?.(error);\n } finally {\n setSaving(false);\n }\n };\n\n const rate = (next: AIChatVote): void => {\n if (currentVote === next) return;\n setLocalVote(next);\n onFeedback?.(message, next);\n };\n\n const showActions = !streaming && !editing;\n const hasAssistantActions =\n onFeedback !== undefined || (canRegenerate && onRegenerate !== undefined);\n\n return (\n <article\n className={cn(\n styles.turn,\n isUser && styles.userTurn,\n role === \"system\" && styles.systemTurn,\n streaming && styles.streamingTurn,\n )}\n aria-busy={streaming || undefined}\n data-role={role}\n >\n <header className={styles.turnHeader}>\n {renderAvatar && (\n <span className={styles.avatar} aria-hidden=\"true\">\n {renderAvatar(message)}\n </span>\n )}\n <span className={cn(styles.role, isUser && styles.visuallyHiddenRole)}>\n {roleLabel(role, strings)}\n </span>\n {model && !isUser && <span className={styles.model}>{model}</span>}\n {createdAt !== undefined && (\n <time className={styles.time} dateTime={new Date(createdAt).toISOString()}>\n {turnTime(createdAt, locale)}\n </time>\n )}\n </header>\n\n {attachments && attachments.length > 0 && (\n <ul className={styles.attachments}>\n {attachments.map((attachment) => (\n <li key={attachment.id} className={styles.attachment}>\n {attachment.url ? (\n <img\n className={styles.thumb}\n src={attachment.url}\n alt={attachment.name}\n />\n ) : (\n <>\n <span className={styles.attachmentName}>{attachment.name}</span>\n <span className={styles.attachmentMeta}>\n {attachment.size !== undefined\n ? formatBytes(attachment.size)\n : attachment.mimeType}\n </span>\n </>\n )}\n </li>\n ))}\n </ul>\n )}\n\n {reasoning && (\n <Collapsible\n className={styles.reasoning}\n defaultOpen={defaultReasoningOpen || (streaming === true && content === \"\")}\n trigger={\n <>\n <ChevronRight\n className={styles.reasoningChevron}\n size={14}\n aria-hidden\n />\n {strings.reasoning}\n </>\n }\n >\n <div className={styles.reasoningBody}>{reasoning}</div>\n </Collapsible>\n )}\n\n {editing ? (\n <div className={styles.editor}>\n <VisuallyHidden>\n <label htmlFor={`${message.id}-edit`}>{strings.editing}</label>\n </VisuallyHidden>\n <textarea\n id={`${message.id}-edit`}\n className={styles.editField}\n value={draft}\n rows={Math.min(draft.split(\"\\n\").length + 1, 10)}\n onChange={(event) => setDraft(event.target.value)}\n />\n <div className={styles.editActions}>\n <button\n type=\"button\"\n className={styles.ghost}\n onClick={() => {\n setDraft(content);\n setEditing(false);\n }}\n >\n {strings.cancel}\n </button>\n <button\n type=\"button\"\n className={styles.primary}\n disabled={saving || draft.trim() === \"\"}\n onClick={() => void submitEdit()}\n >\n {strings.save}\n </button>\n </div>\n </div>\n ) : (\n <div className={cn(styles.body, streaming && styles.streamingBody)}>\n {body}\n {streaming === true && content === \"\" && (\n <span className={styles.dots} aria-hidden=\"true\">\n <span />\n <span />\n <span />\n </span>\n )}\n </div>\n )}\n\n {error && (\n <p className={styles.error} role=\"alert\">\n <span>{error}</span>\n {onRetry && (\n <button\n type=\"button\"\n className={styles.retry}\n onClick={() => onRetry(message)}\n >\n {strings.retry}\n </button>\n )}\n </p>\n )}\n\n {showActions && (\n <div className={styles.actions} aria-label={strings.turnActions} role=\"group\">\n <CopyButton\n className={styles.action}\n value={content}\n aria-label={strings.copy}\n title={strings.copy}\n >\n <Copy size={14} aria-hidden />\n </CopyButton>\n\n {isUser && onEditSubmit && (\n <button\n type=\"button\"\n className={styles.action}\n aria-label={strings.edit}\n title={strings.edit}\n onClick={() => {\n setDraft(content);\n setEditing(true);\n }}\n >\n <Pencil size={14} aria-hidden />\n </button>\n )}\n\n {!isUser && hasAssistantActions && (\n <>\n {canRegenerate && onRegenerate && (\n <button\n type=\"button\"\n className={styles.action}\n aria-label={strings.regenerate}\n title={strings.regenerate}\n onClick={() => onRegenerate(message)}\n >\n <RefreshCw size={14} aria-hidden />\n </button>\n )}\n {onFeedback && (\n <>\n <button\n type=\"button\"\n className={cn(\n styles.action,\n currentVote === \"up\" && styles.actionActive,\n )}\n aria-label={strings.good}\n title={strings.good}\n aria-pressed={currentVote === \"up\"}\n onClick={() => rate(\"up\")}\n >\n <ThumbsUp size={14} aria-hidden />\n </button>\n <button\n type=\"button\"\n className={cn(\n styles.action,\n currentVote === \"down\" && styles.actionActive,\n )}\n aria-label={strings.bad}\n title={strings.bad}\n aria-pressed={currentVote === \"down\"}\n onClick={() => rate(\"down\")}\n >\n <ThumbsDown size={14} aria-hidden />\n </button>\n </>\n )}\n </>\n )}\n </div>\n )}\n </article>\n );\n}\n"],"mappings":"2YAkFA,SAAgB,EAAW,CACvB,UACA,SAAS,QACT,gBAAgB,GAChB,eACA,aACA,eACA,cACA,UACA,eACA,gBACA,OACA,uBAAuB,GACvB,kBAAkB,IACF,CAChB,IAAM,EAAU,EAAA,cAAc,CAAM,EAC9B,CAAE,OAAM,UAAS,YAAW,YAAW,QAAO,QAAO,YAAW,eAAgB,EAChF,CAAC,EAAW,IAAA,EAAA,EAAA,SAAA,CAA4C,IAAI,EAC5D,CAAC,EAAS,IAAA,EAAA,EAAA,SAAA,CAAuB,EAAK,EACtC,CAAC,EAAO,IAAA,EAAA,EAAA,SAAA,CAAqB,CAAO,EACpC,CAAC,EAAQ,IAAA,EAAA,EAAA,SAAA,CAAsB,EAAK,EAEpC,EAAc,GAAQ,EACtB,EAAS,IAAS,OAUlB,GAAA,EAAA,EAAA,QAAA,MAEE,EAAA,EAAA,IAAA,CAAC,EAAA,SAAD,CACI,OAAQ,EACS,kBACjB,UAAW,EAAA,QAAO,YACrB,CAAA,EAEL,CAAC,EAAS,CAAe,CAC7B,EASM,EACE,EAAsB,EAAc,CAAO,EAC3C,GAAe,EAAA,EAAA,IAAA,CAAC,MAAD,CAAK,UAAW,EAAA,QAAO,eAAQ,CAAa,CAAA,EAC3D,IAAY,GAAW,KACpB,EAGL,EAAa,SAA2B,CAC1C,IAAM,EAAO,EAAM,KAAK,EACpB,MAAC,GAAQ,GAAU,CAAC,GACxB,GAAU,EAAI,EACd,GAAI,CACA,MAAM,EAAa,EAAS,CAAI,EAChC,EAAW,EAAK,CACpB,OAAS,EAAO,CACZ,IAAc,CAAK,CACvB,QAAU,CACN,EAAU,EAAK,CACnB,CARc,CASlB,EAEM,EAAQ,GAA2B,CACjC,IAAgB,IACpB,EAAa,CAAI,EACjB,IAAa,EAAS,CAAI,EAC9B,EAEM,EAAc,CAAC,GAAa,CAAC,EAC7B,EACF,IAAe,IAAA,IAAc,GAAiB,IAAiB,IAAA,GAEnE,OACI,EAAA,EAAA,KAAA,CAAC,UAAD,CACI,UAAW,EAAA,GACP,EAAA,QAAO,KACP,GAAU,EAAA,QAAO,SACjB,IAAS,UAAY,EAAA,QAAO,WAC5B,GAAa,EAAA,QAAO,aACxB,EACA,YAAW,GAAa,IAAA,GACxB,YAAW,WARf,EAUI,EAAA,EAAA,KAAA,CAAC,SAAD,CAAQ,UAAW,EAAA,QAAO,oBAA1B,CACK,IACG,EAAA,EAAA,IAAA,CAAC,OAAD,CAAM,UAAW,EAAA,QAAO,OAAQ,cAAY,gBACvC,EAAa,CAAO,CACnB,CAAA,GAEV,EAAA,EAAA,IAAA,CAAC,OAAD,CAAM,UAAW,EAAA,GAAG,EAAA,QAAO,KAAM,GAAU,EAAA,QAAO,kBAAkB,WAC/D,EAAA,UAAU,EAAM,CAAO,CACtB,CAAA,EACL,GAAS,CAAC,IAAU,EAAA,EAAA,IAAA,CAAC,OAAD,CAAM,UAAW,EAAA,QAAO,eAAQ,CAAY,CAAA,EAChE,IAAc,IAAA,KACX,EAAA,EAAA,IAAA,CAAC,OAAD,CAAM,UAAW,EAAA,QAAO,KAAM,SAAU,IAAI,KAAK,CAAS,CAAC,CAAC,YAAY,WACnE,EAAA,SAAS,EAAW,CAAM,CACzB,CAAA,CAEN,IAEP,GAAe,EAAY,OAAS,IACjC,EAAA,EAAA,IAAA,CAAC,KAAD,CAAI,UAAW,EAAA,QAAO,qBACjB,EAAY,IAAK,IACd,EAAA,EAAA,IAAA,CAAC,KAAD,CAAwB,UAAW,EAAA,QAAO,oBACrC,EAAW,KACR,EAAA,EAAA,IAAA,CAAC,MAAD,CACI,UAAW,EAAA,QAAO,MAClB,IAAK,EAAW,IAChB,IAAK,EAAW,IACnB,CAAA,GAED,EAAA,EAAA,KAAA,CAAA,EAAA,SAAA,CAAA,SAAA,EACI,EAAA,EAAA,IAAA,CAAC,OAAD,CAAM,UAAW,EAAA,QAAO,wBAAiB,EAAW,IAAW,CAAA,GAC/D,EAAA,EAAA,IAAA,CAAC,OAAD,CAAM,UAAW,EAAA,QAAO,wBACnB,EAAW,OAAS,IAAA,GAEf,EAAW,SADX,EAAA,YAAY,EAAW,IAAI,CAE/B,CAAA,CACR,CAAA,CAAA,CAEN,EAjBK,EAAW,EAiBhB,CACP,CACD,CAAA,EAGP,IACG,EAAA,EAAA,IAAA,CAAC,EAAA,YAAD,CACI,UAAW,EAAA,QAAO,UAClB,YAAa,GAAyB,IAAc,IAAQ,IAAY,GACxE,SACI,EAAA,EAAA,KAAA,CAAA,EAAA,SAAA,CAAA,SAAA,EACI,EAAA,EAAA,IAAA,CAAC,EAAA,aAAD,CACI,UAAW,EAAA,QAAO,iBAClB,KAAM,GACN,cAAA,EACH,CAAA,EACA,EAAQ,SACX,CAAA,CAAA,YAGN,EAAA,EAAA,IAAA,CAAC,MAAD,CAAK,UAAW,EAAA,QAAO,uBAAgB,CAAe,CAAA,CAC7C,CAAA,EAGhB,GACG,EAAA,EAAA,KAAA,CAAC,MAAD,CAAK,UAAW,EAAA,QAAO,gBAAvB,EACI,EAAA,EAAA,IAAA,CAAC,EAAA,eAAD,CAAA,UACI,EAAA,EAAA,IAAA,CAAC,QAAD,CAAO,QAAS,GAAG,EAAQ,GAAG,gBAAS,EAAQ,OAAe,CAAA,CAClD,CAAA,GAChB,EAAA,EAAA,IAAA,CAAC,WAAD,CACI,GAAI,GAAG,EAAQ,GAAG,OAClB,UAAW,EAAA,QAAO,UAClB,MAAO,EACP,KAAM,KAAK,IAAI,EAAM,MAAM;CAAI,CAAC,CAAC,OAAS,EAAG,EAAE,EAC/C,SAAW,GAAU,EAAS,EAAM,OAAO,KAAK,CACnD,CAAA,GACD,EAAA,EAAA,KAAA,CAAC,MAAD,CAAK,UAAW,EAAA,QAAO,qBAAvB,EACI,EAAA,EAAA,IAAA,CAAC,SAAD,CACI,KAAK,SACL,UAAW,EAAA,QAAO,MAClB,YAAe,CACX,EAAS,CAAO,EAChB,EAAW,EAAK,CACpB,WAEC,EAAQ,MACL,CAAA,GACR,EAAA,EAAA,IAAA,CAAC,SAAD,CACI,KAAK,SACL,UAAW,EAAA,QAAO,QAClB,SAAU,GAAU,EAAM,KAAK,IAAM,GACrC,YAAe,KAAK,EAAW,WAE9B,EAAQ,IACL,CAAA,CACP,GACJ,KAEL,EAAA,EAAA,KAAA,CAAC,MAAD,CAAK,UAAW,EAAA,GAAG,EAAA,QAAO,KAAM,GAAa,EAAA,QAAO,aAAa,WAAjE,CACK,EACA,IAAc,IAAQ,IAAY,KAC/B,EAAA,EAAA,KAAA,CAAC,OAAD,CAAM,UAAW,EAAA,QAAO,KAAM,cAAY,gBAA1C,EACI,EAAA,EAAA,IAAA,CAAC,OAAD,CAAO,CAAA,GACP,EAAA,EAAA,IAAA,CAAC,OAAD,CAAO,CAAA,GACP,EAAA,EAAA,IAAA,CAAC,OAAD,CAAO,CAAA,CACL,GAET,IAGR,IACG,EAAA,EAAA,KAAA,CAAC,IAAD,CAAG,UAAW,EAAA,QAAO,MAAO,KAAK,iBAAjC,EACI,EAAA,EAAA,IAAA,CAAC,OAAD,CAAA,SAAO,CAAY,CAAA,EAClB,IACG,EAAA,EAAA,IAAA,CAAC,SAAD,CACI,KAAK,SACL,UAAW,EAAA,QAAO,MAClB,YAAe,EAAQ,CAAO,WAE7B,EAAQ,KACL,CAAA,CAEb,IAGN,IACG,EAAA,EAAA,KAAA,CAAC,MAAD,CAAK,UAAW,EAAA,QAAO,QAAS,aAAY,EAAQ,YAAa,KAAK,iBAAtE,EACI,EAAA,EAAA,IAAA,CAAC,EAAA,WAAD,CACI,UAAW,EAAA,QAAO,OAClB,MAAO,EACP,aAAY,EAAQ,KACpB,MAAO,EAAQ,eAEf,EAAA,EAAA,IAAA,CAAC,EAAA,KAAD,CAAM,KAAM,GAAI,cAAA,EAAa,CAAA,CACrB,CAAA,EAEX,GAAU,IACP,EAAA,EAAA,IAAA,CAAC,SAAD,CACI,KAAK,SACL,UAAW,EAAA,QAAO,OAClB,aAAY,EAAQ,KACpB,MAAO,EAAQ,KACf,YAAe,CACX,EAAS,CAAO,EAChB,EAAW,EAAI,CACnB,YAEA,EAAA,EAAA,IAAA,CAAC,EAAA,OAAD,CAAQ,KAAM,GAAI,cAAA,EAAa,CAAA,CAC3B,CAAA,EAGX,CAAC,GAAU,IACR,EAAA,EAAA,KAAA,CAAA,EAAA,SAAA,CAAA,SAAA,CACK,GAAiB,IACd,EAAA,EAAA,IAAA,CAAC,SAAD,CACI,KAAK,SACL,UAAW,EAAA,QAAO,OAClB,aAAY,EAAQ,WACpB,MAAO,EAAQ,WACf,YAAe,EAAa,CAAO,YAEnC,EAAA,EAAA,IAAA,CAAC,EAAA,UAAD,CAAW,KAAM,GAAI,cAAA,EAAa,CAAA,CAC9B,CAAA,EAEX,IACG,EAAA,EAAA,KAAA,CAAA,EAAA,SAAA,CAAA,SAAA,EACI,EAAA,EAAA,IAAA,CAAC,SAAD,CACI,KAAK,SACL,UAAW,EAAA,GACP,EAAA,QAAO,OACP,IAAgB,MAAQ,EAAA,QAAO,YACnC,EACA,aAAY,EAAQ,KACpB,MAAO,EAAQ,KACf,eAAc,IAAgB,KAC9B,YAAe,EAAK,IAAI,YAExB,EAAA,EAAA,IAAA,CAAC,EAAA,SAAD,CAAU,KAAM,GAAI,cAAA,EAAa,CAAA,CAC7B,CAAA,GACR,EAAA,EAAA,IAAA,CAAC,SAAD,CACI,KAAK,SACL,UAAW,EAAA,GACP,EAAA,QAAO,OACP,IAAgB,QAAU,EAAA,QAAO,YACrC,EACA,aAAY,EAAQ,IACpB,MAAO,EAAQ,IACf,eAAc,IAAgB,OAC9B,YAAe,EAAK,MAAM,YAE1B,EAAA,EAAA,IAAA,CAAC,EAAA,WAAD,CAAY,KAAM,GAAI,cAAA,EAAa,CAAA,CAC/B,CAAA,CACV,CAAA,CAAA,CAER,CAAA,CAAA,CAEL,GAEJ,GAEjB"}
|
|
1
|
+
{"version":3,"file":"AIChatTurn.cjs","names":[],"sources":["../../../src/components/AIChat/AIChatTurn.tsx"],"sourcesContent":["/**\n * @tempest-limits file-lines, props-count, function-lines — one turn renders six\n * things that each have their own state: Markdown body, collapsible reasoning,\n * attachments, streaming caret, inline edit form and the action row. The 13 props\n * are the callbacks those actions need plus the render slots, and the body is long\n * because the edit form and the action row share the turn's message and its pending\n * state.\n */\nimport { ChevronRight, Copy, Pencil, RefreshCw, ThumbsDown, ThumbsUp } from \"lucide-react\";\nimport { useMemo, useState, type ReactNode } from \"react\";\n\nimport { cn } from \"@/utils/cn\";\nimport { formatBytes } from \"@/utils/numbers\";\n\nimport { Collapsible } from \"../Collapsible\";\nimport { CopyButton } from \"../CopyButton\";\nimport { Markdown } from \"../Markdown\";\nimport { VisuallyHidden } from \"../VisuallyHidden\";\nimport {\n aiChatStrings,\n roleLabel,\n turnTime,\n type AIChatMessage,\n type AIChatVote,\n} from \"./ai-chat-turns\";\nimport styles from \"./AIChat.module.css\";\n\nexport interface AIChatTurnProps {\n /** The turn to render. */\n message: AIChatMessage;\n /** Locale for the labels. Default `\"pt-BR\"`. */\n locale?: \"pt-BR\" | \"en\";\n /**\n * Offer the regenerate control.\n *\n * Only the newest assistant turn should get it — re-asking an older one throws\n * away every turn after it, which is a different operation and needs its own\n * confirmation.\n */\n canRegenerate?: boolean;\n onRegenerate?: (message: AIChatMessage) => void;\n onFeedback?: (message: AIChatMessage, vote: AIChatVote) => void;\n /** Enables the edit control on a user turn. Receives the edited prompt. */\n onEditSubmit?: (message: AIChatMessage, text: string) => void | Promise<void>;\n /**\n * Called when `onEditSubmit` rejects. The editor stays open with the draft either\n * way.\n *\n * Without it the rejection is swallowed after the draft is preserved: re-throwing\n * out of a click handler surfaces as an unhandled promise rejection, which is\n * console noise for the developer and nothing the user can act on.\n */\n onEditError?: (error: unknown) => void;\n /** Enables the retry control on a turn that carries an `error`. */\n onRetry?: (message: AIChatMessage) => void;\n renderAvatar?: (message: AIChatMessage) => ReactNode;\n /** Render the body yourself — a tool-call card, a chart, a citation list. */\n renderContent?: (message: AIChatMessage) => ReactNode;\n /**\n * Rating to show as pressed.\n *\n * Pass it to keep votes in app state (persisted across a reload); leave it out\n * and the pressed state is kept locally, which is enough for a fire-and-forget\n * `onFeedback`.\n */\n vote?: AIChatVote;\n /** Reasoning blocks start expanded. Default `false`. */\n defaultReasoningOpen?: boolean;\n /** Show line numbers in fenced code. Default `false`. */\n showLineNumbers?: boolean;\n}\n\n/**\n * One turn of a conversation with a model.\n *\n * Exported for apps that build their own transcript layout (a split view, a diff of\n * two answers) but still want the SDK's turn: Markdown body, reasoning block,\n * attachments, streaming caret, error state and the action row.\n *\n * @example\n * <AIChatTurn message={turn} canRegenerate onRegenerate={(m) => reask(m)} />\n */\nexport function AIChatTurn({\n message,\n locale = \"pt-BR\",\n canRegenerate = false,\n onRegenerate,\n onFeedback,\n onEditSubmit,\n onEditError,\n onRetry,\n renderAvatar,\n renderContent,\n vote,\n defaultReasoningOpen = false,\n showLineNumbers = false,\n}: AIChatTurnProps) {\n const strings = aiChatStrings(locale);\n const { role, content, reasoning, streaming, error, model, createdAt, attachments } = message;\n const [localVote, setLocalVote] = useState<AIChatVote | null>(null);\n const [editing, setEditing] = useState(false);\n const [draft, setDraft] = useState(content);\n const [saving, setSaving] = useState(false);\n\n const currentVote = vote ?? localVote;\n const isUser = role === \"user\";\n\n /**\n * Memoised so a streaming answer re-parses **only the turn that is growing**.\n *\n * `Markdown` parses in its own render, and React skips re-rendering a child whose\n * element is referentially identical to the previous one. Holding the element\n * across renders is therefore what keeps a fifty-turn transcript from re-parsing\n * every finished answer on every token of the newest one.\n */\n const markdown = useMemo(\n () => (\n <Markdown\n source={content}\n showLineNumbers={showLineNumbers}\n className={styles.markdownRoot}\n />\n ),\n [content, showLineNumbers],\n );\n\n /**\n * Body of the turn.\n *\n * A user turn is plain text with newlines preserved, an assistant turn is\n * Markdown. A model emits Markdown by contract; a person typing `2 * 3 * 4` did\n * not mean to open an emphasis span.\n */\n const body = ((): ReactNode => {\n if (renderContent) return renderContent(message);\n if (isUser) return <div className={styles.plain}>{content}</div>;\n if (content === \"\") return null;\n return markdown;\n })();\n\n const submitEdit = async (): Promise<void> => {\n const text = draft.trim();\n if (!text || saving || !onEditSubmit) return;\n setSaving(true);\n try {\n await onEditSubmit(message, text);\n setEditing(false);\n } catch (error) {\n onEditError?.(error);\n } finally {\n setSaving(false);\n }\n };\n\n const rate = (next: AIChatVote): void => {\n if (currentVote === next) return;\n setLocalVote(next);\n onFeedback?.(message, next);\n };\n\n const showActions = !streaming && !editing;\n const hasAssistantActions =\n onFeedback !== undefined || (canRegenerate && onRegenerate !== undefined);\n\n return (\n <article\n className={cn(\n styles.turn,\n isUser && styles.userTurn,\n role === \"system\" && styles.systemTurn,\n streaming && styles.streamingTurn,\n )}\n aria-busy={streaming || undefined}\n data-role={role}\n >\n <header className={styles.turnHeader}>\n {renderAvatar && (\n <span className={styles.avatar} aria-hidden=\"true\">\n {renderAvatar(message)}\n </span>\n )}\n <span className={cn(styles.role, isUser && styles.visuallyHiddenRole)}>\n {roleLabel(role, strings)}\n </span>\n {model && !isUser && <span className={styles.model}>{model}</span>}\n {createdAt !== undefined && (\n <time className={styles.time} dateTime={new Date(createdAt).toISOString()}>\n {turnTime(createdAt, locale)}\n </time>\n )}\n </header>\n\n {attachments && attachments.length > 0 && (\n <ul className={styles.attachments}>\n {attachments.map((attachment) => (\n <li key={attachment.id} className={styles.attachment}>\n {attachment.url ? (\n <img\n className={styles.thumb}\n src={attachment.url}\n alt={attachment.name}\n />\n ) : (\n <>\n <span className={styles.attachmentName}>{attachment.name}</span>\n <span className={styles.attachmentMeta}>\n {attachment.size !== undefined\n ? formatBytes(attachment.size)\n : attachment.mimeType}\n </span>\n </>\n )}\n </li>\n ))}\n </ul>\n )}\n\n {reasoning && (\n <Collapsible\n className={styles.reasoning}\n defaultOpen={defaultReasoningOpen || (streaming === true && content === \"\")}\n trigger={\n <>\n <ChevronRight\n className={styles.reasoningChevron}\n size={14}\n aria-hidden\n />\n {strings.reasoning}\n </>\n }\n >\n <div className={styles.reasoningBody}>{reasoning}</div>\n </Collapsible>\n )}\n\n {editing ? (\n <div className={styles.editor}>\n <VisuallyHidden>\n <label htmlFor={`${message.id}-edit`}>{strings.editing}</label>\n </VisuallyHidden>\n <textarea\n id={`${message.id}-edit`}\n className={styles.editField}\n value={draft}\n rows={Math.min(draft.split(\"\\n\").length + 1, 10)}\n onChange={(event) => setDraft(event.target.value)}\n />\n <div className={styles.editActions}>\n <button\n type=\"button\"\n className={styles.ghost}\n onClick={() => {\n setDraft(content);\n setEditing(false);\n }}\n >\n {strings.cancel}\n </button>\n <button\n type=\"button\"\n className={styles.primary}\n disabled={saving || draft.trim() === \"\"}\n onClick={() => void submitEdit()}\n >\n {strings.save}\n </button>\n </div>\n </div>\n ) : (\n <div className={cn(styles.body, streaming && styles.streamingBody)}>\n {body}\n {streaming === true && content === \"\" && (\n <span className={styles.dots} aria-hidden=\"true\">\n <span />\n <span />\n <span />\n </span>\n )}\n </div>\n )}\n\n {error && (\n <p className={styles.error} role=\"alert\">\n <span>{error}</span>\n {onRetry && (\n <button\n type=\"button\"\n className={styles.retry}\n onClick={() => onRetry(message)}\n >\n {strings.retry}\n </button>\n )}\n </p>\n )}\n\n {showActions && (\n <div className={styles.actions} aria-label={strings.turnActions} role=\"group\">\n <CopyButton\n className={styles.action}\n value={content}\n aria-label={strings.copy}\n title={strings.copy}\n >\n <Copy size={14} aria-hidden />\n </CopyButton>\n\n {isUser && onEditSubmit && (\n <button\n type=\"button\"\n className={styles.action}\n aria-label={strings.edit}\n title={strings.edit}\n onClick={() => {\n setDraft(content);\n setEditing(true);\n }}\n >\n <Pencil size={14} aria-hidden />\n </button>\n )}\n\n {!isUser && hasAssistantActions && (\n <>\n {canRegenerate && onRegenerate && (\n <button\n type=\"button\"\n className={styles.action}\n aria-label={strings.regenerate}\n title={strings.regenerate}\n onClick={() => onRegenerate(message)}\n >\n <RefreshCw size={14} aria-hidden />\n </button>\n )}\n {onFeedback && (\n <>\n <button\n type=\"button\"\n className={cn(\n styles.action,\n currentVote === \"up\" && styles.actionActive,\n )}\n aria-label={strings.good}\n title={strings.good}\n aria-pressed={currentVote === \"up\"}\n onClick={() => rate(\"up\")}\n >\n <ThumbsUp size={14} aria-hidden />\n </button>\n <button\n type=\"button\"\n className={cn(\n styles.action,\n currentVote === \"down\" && styles.actionActive,\n )}\n aria-label={strings.bad}\n title={strings.bad}\n aria-pressed={currentVote === \"down\"}\n onClick={() => rate(\"down\")}\n >\n <ThumbsDown size={14} aria-hidden />\n </button>\n </>\n )}\n </>\n )}\n </div>\n )}\n </article>\n );\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AIChatTurn.js","names":[],"sources":["../../../src/components/AIChat/AIChatTurn.tsx"],"sourcesContent":["/**\n * @tempest-limits file-lines, props-count, function-lines — one turn renders six\n * things that each have their own state: Markdown body, collapsible reasoning,\n * attachments, streaming caret, inline edit form and the action row. The 13 props\n * are the callbacks those actions need plus the render slots, and the body is long\n * because the edit form and the action row share the turn's message and its pending\n * state.\n */\nimport { ChevronRight, Copy, Pencil, RefreshCw, ThumbsDown, ThumbsUp } from \"lucide-react\";\nimport { useMemo, useState, type ReactNode } from \"react\";\n\nimport { cn } from \"@/utils/cn\";\nimport { formatBytes } from \"@/utils/numbers\";\n\nimport { Collapsible } from \"../Collapsible\";\nimport { CopyButton } from \"../CopyButton\";\nimport { Markdown } from \"../Markdown\";\nimport { VisuallyHidden } from \"../VisuallyHidden\";\nimport {\n aiChatStrings,\n roleLabel,\n turnTime,\n type AIChatMessage,\n type AIChatVote,\n} from \"./ai-chat-turns\";\nimport styles from \"./AIChat.module.css\";\n\nexport interface AIChatTurnProps {\n /** The turn to render. */\n message: AIChatMessage;\n /** Locale for the labels. Default `\"pt-BR\"`. */\n locale?: \"pt-BR\" | \"en\";\n /**\n * Offer the regenerate control.\n *\n * Only the newest assistant turn should get it — re-asking an older one throws\n * away every turn after it, which is a different operation and needs its own\n * confirmation.\n */\n canRegenerate?: boolean;\n onRegenerate?: (message: AIChatMessage) => void;\n onFeedback?: (message: AIChatMessage, vote: AIChatVote) => void;\n /** Enables the edit control on a user turn. Receives the edited prompt. */\n onEditSubmit?: (message: AIChatMessage, text: string) => void | Promise<void>;\n /**\n * Called when `onEditSubmit` rejects. The editor stays open with the draft either\n * way.\n *\n * Without it the rejection is swallowed after the draft is preserved: re-throwing\n * out of a click handler surfaces as an unhandled promise rejection, which is\n * console noise for the developer and nothing the user can act on.\n */\n onEditError?: (error: unknown) => void;\n /** Enables the retry control on a turn that carries an `error`. */\n onRetry?: (message: AIChatMessage) => void;\n renderAvatar?: (message: AIChatMessage) => ReactNode;\n /** Render the body yourself — a tool-call card, a chart, a citation list. */\n renderContent?: (message: AIChatMessage) => ReactNode;\n /**\n * Rating to show as pressed.\n *\n * Pass it to keep votes in app state (persisted across a reload); leave it out\n * and the pressed state is kept locally, which is enough for a fire-and-forget\n * `onFeedback`.\n */\n vote?: AIChatVote;\n /** Reasoning blocks start expanded. Default `false`. */\n defaultReasoningOpen?: boolean;\n /** Show line numbers in fenced code. Default `false`. */\n showLineNumbers?: boolean;\n}\n\n/**\n * One turn of a conversation with a model.\n *\n * Exported for apps that build their own transcript layout (a split view, a diff of\n * two answers) but still want the SDK's turn: Markdown body, reasoning block,\n * attachments, streaming caret, error state and the action row.\n *\n * @example\n * <AIChatTurn message={turn} canRegenerate onRegenerate={(m) => reask(m)} />\n */\nexport function AIChatTurn({\n message,\n locale = \"pt-BR\",\n canRegenerate = false,\n onRegenerate,\n onFeedback,\n onEditSubmit,\n onEditError,\n onRetry,\n renderAvatar,\n renderContent,\n vote,\n defaultReasoningOpen = false,\n showLineNumbers = false,\n}: AIChatTurnProps) {\n const strings = aiChatStrings(locale);\n const { role, content, reasoning, streaming, error, model, createdAt, attachments } = message;\n const [localVote, setLocalVote] = useState<AIChatVote | null>(null);\n const [editing, setEditing] = useState(false);\n const [draft, setDraft] = useState(content);\n const [saving, setSaving] = useState(false);\n\n const currentVote = vote ?? localVote;\n const isUser = role === \"user\";\n\n /**\n * Memoised so a streaming answer re-parses **only the turn that is growing**.\n *\n * `Markdown` parses in its own render, and React skips re-rendering a child whose\n * element is referentially identical to the previous one. Holding the element\n * across renders is therefore what keeps a fifty-turn transcript from re-parsing\n * every finished answer on every token of the newest one.\n */\n const markdown = useMemo(\n () => (\n <Markdown\n source={content}\n showLineNumbers={showLineNumbers}\n className={styles.markdownRoot}\n />\n ),\n [content, showLineNumbers],\n );\n\n /**\n * Body of the turn.\n *\n * A user turn is plain text with newlines preserved, an assistant turn is\n * Markdown. A model emits Markdown by contract; a person typing `2 * 3 * 4` did\n * not mean to open an emphasis span.\n */\n const body = ((): ReactNode => {\n if (renderContent) return renderContent(message);\n if (isUser) return <div className={styles.plain}>{content}</div>;\n if (content === \"\") return null;\n return markdown;\n })();\n\n const submitEdit = async (): Promise<void> => {\n const text = draft.trim();\n if (!text || saving || !onEditSubmit) return;\n setSaving(true);\n try {\n await onEditSubmit(message, text);\n setEditing(false);\n } catch (error) {\n onEditError?.(error);\n } finally {\n setSaving(false);\n }\n };\n\n const rate = (next: AIChatVote): void => {\n if (currentVote === next) return;\n setLocalVote(next);\n onFeedback?.(message, next);\n };\n\n const showActions = !streaming && !editing;\n const hasAssistantActions =\n onFeedback !== undefined || (canRegenerate && onRegenerate !== undefined);\n\n return (\n <article\n className={cn(\n styles.turn,\n isUser && styles.userTurn,\n role === \"system\" && styles.systemTurn,\n streaming && styles.streamingTurn,\n )}\n aria-busy={streaming || undefined}\n data-role={role}\n >\n <header className={styles.turnHeader}>\n {renderAvatar && (\n <span className={styles.avatar} aria-hidden=\"true\">\n {renderAvatar(message)}\n </span>\n )}\n <span className={cn(styles.role, isUser && styles.visuallyHiddenRole)}>\n {roleLabel(role, strings)}\n </span>\n {model && !isUser && <span className={styles.model}>{model}</span>}\n {createdAt !== undefined && (\n <time className={styles.time} dateTime={new Date(createdAt).toISOString()}>\n {turnTime(createdAt, locale)}\n </time>\n )}\n </header>\n\n {attachments && attachments.length > 0 && (\n <ul className={styles.attachments}>\n {attachments.map((attachment) => (\n <li key={attachment.id} className={styles.attachment}>\n {attachment.url ? (\n <img\n className={styles.thumb}\n src={attachment.url}\n alt={attachment.name}\n />\n ) : (\n <>\n <span className={styles.attachmentName}>{attachment.name}</span>\n <span className={styles.attachmentMeta}>\n {attachment.size !== undefined\n ? formatBytes(attachment.size)\n : attachment.mimeType}\n </span>\n </>\n )}\n </li>\n ))}\n </ul>\n )}\n\n {reasoning && (\n <Collapsible\n className={styles.reasoning}\n defaultOpen={defaultReasoningOpen || (streaming === true && content === \"\")}\n trigger={\n <>\n <ChevronRight\n className={styles.reasoningChevron}\n size={14}\n aria-hidden\n />\n {strings.reasoning}\n </>\n }\n >\n <div className={styles.reasoningBody}>{reasoning}</div>\n </Collapsible>\n )}\n\n {editing ? (\n <div className={styles.editor}>\n <VisuallyHidden>\n <label htmlFor={`${message.id}-edit`}>{strings.editing}</label>\n </VisuallyHidden>\n <textarea\n id={`${message.id}-edit`}\n className={styles.editField}\n value={draft}\n rows={Math.min(draft.split(\"\\n\").length + 1, 10)}\n onChange={(event) => setDraft(event.target.value)}\n />\n <div className={styles.editActions}>\n <button\n type=\"button\"\n className={styles.ghost}\n onClick={() => {\n setDraft(content);\n setEditing(false);\n }}\n >\n {strings.cancel}\n </button>\n <button\n type=\"button\"\n className={styles.primary}\n disabled={saving || draft.trim() === \"\"}\n onClick={() => void submitEdit()}\n >\n {strings.save}\n </button>\n </div>\n </div>\n ) : (\n <div className={cn(styles.body, streaming && styles.streamingBody)}>\n {body}\n {streaming === true && content === \"\" && (\n <span className={styles.dots} aria-hidden=\"true\">\n <span />\n <span />\n <span />\n </span>\n )}\n </div>\n )}\n\n {error && (\n <p className={styles.error} role=\"alert\">\n <span>{error}</span>\n {onRetry && (\n <button\n type=\"button\"\n className={styles.retry}\n onClick={() => onRetry(message)}\n >\n {strings.retry}\n </button>\n )}\n </p>\n )}\n\n {showActions && (\n <div className={styles.actions} aria-label={strings.turnActions} role=\"group\">\n <CopyButton\n className={styles.action}\n value={content}\n aria-label={strings.copy}\n title={strings.copy}\n >\n <Copy size={14} aria-hidden />\n </CopyButton>\n\n {isUser && onEditSubmit && (\n <button\n type=\"button\"\n className={styles.action}\n aria-label={strings.edit}\n title={strings.edit}\n onClick={() => {\n setDraft(content);\n setEditing(true);\n }}\n >\n <Pencil size={14} aria-hidden />\n </button>\n )}\n\n {!isUser && hasAssistantActions && (\n <>\n {canRegenerate && onRegenerate && (\n <button\n type=\"button\"\n className={styles.action}\n aria-label={strings.regenerate}\n title={strings.regenerate}\n onClick={() => onRegenerate(message)}\n >\n <RefreshCw size={14} aria-hidden />\n </button>\n )}\n {onFeedback && (\n <>\n <button\n type=\"button\"\n className={cn(\n styles.action,\n currentVote === \"up\" && styles.actionActive,\n )}\n aria-label={strings.good}\n title={strings.good}\n aria-pressed={currentVote === \"up\"}\n onClick={() => rate(\"up\")}\n >\n <ThumbsUp size={14} aria-hidden />\n </button>\n <button\n type=\"button\"\n className={cn(\n styles.action,\n currentVote === \"down\" && styles.actionActive,\n )}\n aria-label={strings.bad}\n title={strings.bad}\n aria-pressed={currentVote === \"down\"}\n onClick={() => rate(\"down\")}\n >\n <ThumbsDown size={14} aria-hidden />\n </button>\n </>\n )}\n </>\n )}\n </div>\n )}\n </article>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"AIChatTurn.js","names":[],"sources":["../../../src/components/AIChat/AIChatTurn.tsx"],"sourcesContent":["/**\n * @tempest-limits file-lines, props-count, function-lines — one turn renders six\n * things that each have their own state: Markdown body, collapsible reasoning,\n * attachments, streaming caret, inline edit form and the action row. The 13 props\n * are the callbacks those actions need plus the render slots, and the body is long\n * because the edit form and the action row share the turn's message and its pending\n * state.\n */\nimport { ChevronRight, Copy, Pencil, RefreshCw, ThumbsDown, ThumbsUp } from \"lucide-react\";\nimport { useMemo, useState, type ReactNode } from \"react\";\n\nimport { cn } from \"@/utils/cn\";\nimport { formatBytes } from \"@/utils/numbers\";\n\nimport { Collapsible } from \"../Collapsible\";\nimport { CopyButton } from \"../CopyButton\";\nimport { Markdown } from \"../Markdown\";\nimport { VisuallyHidden } from \"../VisuallyHidden\";\nimport {\n aiChatStrings,\n roleLabel,\n turnTime,\n type AIChatMessage,\n type AIChatVote,\n} from \"./ai-chat-turns\";\nimport styles from \"./AIChat.module.css\";\n\nexport interface AIChatTurnProps {\n /** The turn to render. */\n message: AIChatMessage;\n /** Locale for the labels. Default `\"pt-BR\"`. */\n locale?: \"pt-BR\" | \"en\";\n /**\n * Offer the regenerate control.\n *\n * Only the newest assistant turn should get it — re-asking an older one throws\n * away every turn after it, which is a different operation and needs its own\n * confirmation.\n */\n canRegenerate?: boolean;\n onRegenerate?: (message: AIChatMessage) => void;\n onFeedback?: (message: AIChatMessage, vote: AIChatVote) => void;\n /** Enables the edit control on a user turn. Receives the edited prompt. */\n onEditSubmit?: (message: AIChatMessage, text: string) => void | Promise<void>;\n /**\n * Called when `onEditSubmit` rejects. The editor stays open with the draft either\n * way.\n *\n * Without it the rejection is swallowed after the draft is preserved: re-throwing\n * out of a click handler surfaces as an unhandled promise rejection, which is\n * console noise for the developer and nothing the user can act on.\n */\n onEditError?: (error: unknown) => void;\n /** Enables the retry control on a turn that carries an `error`. */\n onRetry?: (message: AIChatMessage) => void;\n renderAvatar?: (message: AIChatMessage) => ReactNode;\n /** Render the body yourself — a tool-call card, a chart, a citation list. */\n renderContent?: (message: AIChatMessage) => ReactNode;\n /**\n * Rating to show as pressed.\n *\n * Pass it to keep votes in app state (persisted across a reload); leave it out\n * and the pressed state is kept locally, which is enough for a fire-and-forget\n * `onFeedback`.\n */\n vote?: AIChatVote;\n /** Reasoning blocks start expanded. Default `false`. */\n defaultReasoningOpen?: boolean;\n /** Show line numbers in fenced code. Default `false`. */\n showLineNumbers?: boolean;\n}\n\n/**\n * One turn of a conversation with a model.\n *\n * Exported for apps that build their own transcript layout (a split view, a diff of\n * two answers) but still want the SDK's turn: Markdown body, reasoning block,\n * attachments, streaming caret, error state and the action row.\n *\n * @example\n * <AIChatTurn message={turn} canRegenerate onRegenerate={(m) => reask(m)} />\n */\nexport function AIChatTurn({\n message,\n locale = \"pt-BR\",\n canRegenerate = false,\n onRegenerate,\n onFeedback,\n onEditSubmit,\n onEditError,\n onRetry,\n renderAvatar,\n renderContent,\n vote,\n defaultReasoningOpen = false,\n showLineNumbers = false,\n}: AIChatTurnProps) {\n const strings = aiChatStrings(locale);\n const { role, content, reasoning, streaming, error, model, createdAt, attachments } = message;\n const [localVote, setLocalVote] = useState<AIChatVote | null>(null);\n const [editing, setEditing] = useState(false);\n const [draft, setDraft] = useState(content);\n const [saving, setSaving] = useState(false);\n\n const currentVote = vote ?? localVote;\n const isUser = role === \"user\";\n\n /**\n * Memoised so a streaming answer re-parses **only the turn that is growing**.\n *\n * `Markdown` parses in its own render, and React skips re-rendering a child whose\n * element is referentially identical to the previous one. Holding the element\n * across renders is therefore what keeps a fifty-turn transcript from re-parsing\n * every finished answer on every token of the newest one.\n */\n const markdown = useMemo(\n () => (\n <Markdown\n source={content}\n showLineNumbers={showLineNumbers}\n className={styles.markdownRoot}\n />\n ),\n [content, showLineNumbers],\n );\n\n /**\n * Body of the turn.\n *\n * A user turn is plain text with newlines preserved, an assistant turn is\n * Markdown. A model emits Markdown by contract; a person typing `2 * 3 * 4` did\n * not mean to open an emphasis span.\n */\n const body = ((): ReactNode => {\n if (renderContent) return renderContent(message);\n if (isUser) return <div className={styles.plain}>{content}</div>;\n if (content === \"\") return null;\n return markdown;\n })();\n\n const submitEdit = async (): Promise<void> => {\n const text = draft.trim();\n if (!text || saving || !onEditSubmit) return;\n setSaving(true);\n try {\n await onEditSubmit(message, text);\n setEditing(false);\n } catch (error) {\n onEditError?.(error);\n } finally {\n setSaving(false);\n }\n };\n\n const rate = (next: AIChatVote): void => {\n if (currentVote === next) return;\n setLocalVote(next);\n onFeedback?.(message, next);\n };\n\n const showActions = !streaming && !editing;\n const hasAssistantActions =\n onFeedback !== undefined || (canRegenerate && onRegenerate !== undefined);\n\n return (\n <article\n className={cn(\n styles.turn,\n isUser && styles.userTurn,\n role === \"system\" && styles.systemTurn,\n streaming && styles.streamingTurn,\n )}\n aria-busy={streaming || undefined}\n data-role={role}\n >\n <header className={styles.turnHeader}>\n {renderAvatar && (\n <span className={styles.avatar} aria-hidden=\"true\">\n {renderAvatar(message)}\n </span>\n )}\n <span className={cn(styles.role, isUser && styles.visuallyHiddenRole)}>\n {roleLabel(role, strings)}\n </span>\n {model && !isUser && <span className={styles.model}>{model}</span>}\n {createdAt !== undefined && (\n <time className={styles.time} dateTime={new Date(createdAt).toISOString()}>\n {turnTime(createdAt, locale)}\n </time>\n )}\n </header>\n\n {attachments && attachments.length > 0 && (\n <ul className={styles.attachments}>\n {attachments.map((attachment) => (\n <li key={attachment.id} className={styles.attachment}>\n {attachment.url ? (\n <img\n className={styles.thumb}\n src={attachment.url}\n alt={attachment.name}\n />\n ) : (\n <>\n <span className={styles.attachmentName}>{attachment.name}</span>\n <span className={styles.attachmentMeta}>\n {attachment.size !== undefined\n ? formatBytes(attachment.size)\n : attachment.mimeType}\n </span>\n </>\n )}\n </li>\n ))}\n </ul>\n )}\n\n {reasoning && (\n <Collapsible\n className={styles.reasoning}\n defaultOpen={defaultReasoningOpen || (streaming === true && content === \"\")}\n trigger={\n <>\n <ChevronRight\n className={styles.reasoningChevron}\n size={14}\n aria-hidden\n />\n {strings.reasoning}\n </>\n }\n >\n <div className={styles.reasoningBody}>{reasoning}</div>\n </Collapsible>\n )}\n\n {editing ? (\n <div className={styles.editor}>\n <VisuallyHidden>\n <label htmlFor={`${message.id}-edit`}>{strings.editing}</label>\n </VisuallyHidden>\n <textarea\n id={`${message.id}-edit`}\n className={styles.editField}\n value={draft}\n rows={Math.min(draft.split(\"\\n\").length + 1, 10)}\n onChange={(event) => setDraft(event.target.value)}\n />\n <div className={styles.editActions}>\n <button\n type=\"button\"\n className={styles.ghost}\n onClick={() => {\n setDraft(content);\n setEditing(false);\n }}\n >\n {strings.cancel}\n </button>\n <button\n type=\"button\"\n className={styles.primary}\n disabled={saving || draft.trim() === \"\"}\n onClick={() => void submitEdit()}\n >\n {strings.save}\n </button>\n </div>\n </div>\n ) : (\n <div className={cn(styles.body, streaming && styles.streamingBody)}>\n {body}\n {streaming === true && content === \"\" && (\n <span className={styles.dots} aria-hidden=\"true\">\n <span />\n <span />\n <span />\n </span>\n )}\n </div>\n )}\n\n {error && (\n <p className={styles.error} role=\"alert\">\n <span>{error}</span>\n {onRetry && (\n <button\n type=\"button\"\n className={styles.retry}\n onClick={() => onRetry(message)}\n >\n {strings.retry}\n </button>\n )}\n </p>\n )}\n\n {showActions && (\n <div className={styles.actions} aria-label={strings.turnActions} role=\"group\">\n <CopyButton\n className={styles.action}\n value={content}\n aria-label={strings.copy}\n title={strings.copy}\n >\n <Copy size={14} aria-hidden />\n </CopyButton>\n\n {isUser && onEditSubmit && (\n <button\n type=\"button\"\n className={styles.action}\n aria-label={strings.edit}\n title={strings.edit}\n onClick={() => {\n setDraft(content);\n setEditing(true);\n }}\n >\n <Pencil size={14} aria-hidden />\n </button>\n )}\n\n {!isUser && hasAssistantActions && (\n <>\n {canRegenerate && onRegenerate && (\n <button\n type=\"button\"\n className={styles.action}\n aria-label={strings.regenerate}\n title={strings.regenerate}\n onClick={() => onRegenerate(message)}\n >\n <RefreshCw size={14} aria-hidden />\n </button>\n )}\n {onFeedback && (\n <>\n <button\n type=\"button\"\n className={cn(\n styles.action,\n currentVote === \"up\" && styles.actionActive,\n )}\n aria-label={strings.good}\n title={strings.good}\n aria-pressed={currentVote === \"up\"}\n onClick={() => rate(\"up\")}\n >\n <ThumbsUp size={14} aria-hidden />\n </button>\n <button\n type=\"button\"\n className={cn(\n styles.action,\n currentVote === \"down\" && styles.actionActive,\n )}\n aria-label={strings.bad}\n title={strings.bad}\n aria-pressed={currentVote === \"down\"}\n onClick={() => rate(\"down\")}\n >\n <ThumbsDown size={14} aria-hidden />\n </button>\n </>\n )}\n </>\n )}\n </div>\n )}\n </article>\n );\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Accordion.cjs","names":[],"sources":["../../../src/components/Accordion/Accordion.tsx"],"sourcesContent":["import { useCallback, useId, useState } from \"react\";\nimport type { ReactNode } from \"react\";\nimport { cn } from \"@/utils/cn\";\nimport styles from \"./Accordion.module.css\";\n\nexport interface AccordionItem {\n /** Stable identifier. */\n id: string;\n title: ReactNode;\n children: ReactNode;\n disabled?: boolean;\n}\n\nexport interface AccordionProps {\n items: AccordionItem[];\n /** When `true` multiple items can be open simultaneously. Default `false`. */\n multiple?: boolean;\n /** Controlled open ids. */\n value?: string[];\n /** Uncontrolled default open ids. */\n defaultValue?: string[];\n onChange?: (openIds: string[]) => void;\n className?: string;\n}\n\n/**\n * Accessible accordion. Each item collapses/expands its content. Single-mode by\n * default — pass `multiple` to allow more than one item open at a time. Can be\n * controlled via `value` + `onChange`, or uncontrolled via `defaultValue`.\n */\nexport function Accordion({\n items,\n multiple = false,\n value,\n defaultValue = [],\n onChange,\n className,\n}: AccordionProps) {\n const reactId = useId();\n const isControlled = value !== undefined;\n const [internalOpen, setInternalOpen] = useState<string[]>(defaultValue);\n const openIds = isControlled ? value : internalOpen;\n\n const toggle = useCallback(\n (id: string): void => {\n const isOpen = openIds.includes(id);\n const next = multiple\n ? isOpen\n ? openIds.filter((x) => x !== id)\n : [...openIds, id]\n : isOpen\n ? []\n : [id];\n if (!isControlled) setInternalOpen(next);\n onChange?.(next);\n },\n [openIds, multiple, isControlled, onChange],\n );\n\n return (\n <div className={cn(styles.accordion, className)}>\n {items.map((item) => {\n const open = openIds.includes(item.id);\n const panelId = `${reactId}-${item.id}-panel`;\n const triggerId = `${reactId}-${item.id}-trigger`;\n return (\n <div key={item.id} className={cn(styles.item, open && styles.open)}>\n <h3 className={styles.header}>\n <button\n type=\"button\"\n id={triggerId}\n className={styles.trigger}\n aria-expanded={open}\n aria-controls={panelId}\n disabled={item.disabled}\n onClick={() => toggle(item.id)}\n >\n <span>{item.title}</span>\n <ChevronIcon className={styles.chevron} />\n </button>\n </h3>\n {open && (\n <div\n id={panelId}\n role=\"region\"\n aria-labelledby={triggerId}\n className={styles.panel}\n >\n {item.children}\n </div>\n )}\n </div>\n );\n })}\n </div>\n );\n}\n\nfunction ChevronIcon({ className }: { className?: string }) {\n return (\n <svg\n className={className}\n width=\"16\"\n height=\"16\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n aria-hidden\n >\n <path\n d=\"M6 9l6 6 6-6\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n />\n </svg>\n );\n}\n"],"mappings":"gIA8BA,SAAgB,EAAU,CACtB,QACA,WAAW,GACX,QACA,eAAe,CAAC,EAChB,WACA,aACe,CACf,IAAM,GAAA,
|
|
1
|
+
{"version":3,"file":"Accordion.cjs","names":[],"sources":["../../../src/components/Accordion/Accordion.tsx"],"sourcesContent":["import { useCallback, useId, useState } from \"react\";\nimport type { ReactNode } from \"react\";\nimport { cn } from \"@/utils/cn\";\nimport styles from \"./Accordion.module.css\";\n\nexport interface AccordionItem {\n /** Stable identifier. */\n id: string;\n title: ReactNode;\n children: ReactNode;\n disabled?: boolean;\n}\n\nexport interface AccordionProps {\n items: AccordionItem[];\n /** When `true` multiple items can be open simultaneously. Default `false`. */\n multiple?: boolean;\n /** Controlled open ids. */\n value?: string[];\n /** Uncontrolled default open ids. */\n defaultValue?: string[];\n onChange?: (openIds: string[]) => void;\n className?: string;\n}\n\n/**\n * Accessible accordion. Each item collapses/expands its content. Single-mode by\n * default — pass `multiple` to allow more than one item open at a time. Can be\n * controlled via `value` + `onChange`, or uncontrolled via `defaultValue`.\n */\nexport function Accordion({\n items,\n multiple = false,\n value,\n defaultValue = [],\n onChange,\n className,\n}: AccordionProps) {\n const reactId = useId();\n const isControlled = value !== undefined;\n const [internalOpen, setInternalOpen] = useState<string[]>(defaultValue);\n const openIds = isControlled ? value : internalOpen;\n\n const toggle = useCallback(\n (id: string): void => {\n const isOpen = openIds.includes(id);\n const next = multiple\n ? isOpen\n ? openIds.filter((x) => x !== id)\n : [...openIds, id]\n : isOpen\n ? []\n : [id];\n if (!isControlled) setInternalOpen(next);\n onChange?.(next);\n },\n [openIds, multiple, isControlled, onChange],\n );\n\n return (\n <div className={cn(styles.accordion, className)}>\n {items.map((item) => {\n const open = openIds.includes(item.id);\n const panelId = `${reactId}-${item.id}-panel`;\n const triggerId = `${reactId}-${item.id}-trigger`;\n return (\n <div key={item.id} className={cn(styles.item, open && styles.open)}>\n <h3 className={styles.header}>\n <button\n type=\"button\"\n id={triggerId}\n className={styles.trigger}\n aria-expanded={open}\n aria-controls={panelId}\n disabled={item.disabled}\n onClick={() => toggle(item.id)}\n >\n <span>{item.title}</span>\n <ChevronIcon className={styles.chevron} />\n </button>\n </h3>\n {open && (\n <div\n id={panelId}\n role=\"region\"\n aria-labelledby={triggerId}\n className={styles.panel}\n >\n {item.children}\n </div>\n )}\n </div>\n );\n })}\n </div>\n );\n}\n\nfunction ChevronIcon({ className }: { className?: string }) {\n return (\n <svg\n className={className}\n width=\"16\"\n height=\"16\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n aria-hidden\n >\n <path\n d=\"M6 9l6 6 6-6\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n />\n </svg>\n );\n}\n"],"mappings":"gIA8BA,SAAgB,EAAU,CACtB,QACA,WAAW,GACX,QACA,eAAe,CAAC,EAChB,WACA,aACe,CACf,IAAM,GAAA,EAAU,EAAA,MAAA,CAAM,EAChB,EAAe,IAAU,IAAA,GACzB,CAAC,EAAc,IAAA,EAAmB,EAAA,SAAA,CAAmB,CAAY,EACjE,EAAU,EAAe,EAAQ,EAEjC,GAAA,EAAS,EAAA,YAAA,CACV,GAAqB,CAClB,IAAM,EAAS,EAAQ,SAAS,CAAE,EAC5B,EAAO,EACP,EACI,EAAQ,OAAQ,GAAM,IAAM,CAAE,EAC9B,CAAC,GAAG,EAAS,CAAE,EACnB,EACE,CAAC,EACD,CAAC,CAAE,EACN,GAAc,EAAgB,CAAI,EACvC,IAAW,CAAI,CACnB,EACA,CAAC,EAAS,EAAU,EAAc,CAAQ,CAC9C,EAEA,OACI,EAAA,EAAA,IAAA,CAAC,MAAD,CAAK,UAAW,EAAA,GAAG,EAAA,QAAO,UAAW,CAAS,EACzC,SAAA,EAAM,IAAK,GAAS,CACjB,IAAM,EAAO,EAAQ,SAAS,EAAK,EAAE,EAC/B,EAAU,GAAG,EAAQ,GAAG,EAAK,GAAG,QAChC,EAAY,GAAG,EAAQ,GAAG,EAAK,GAAG,UACxC,OACI,EAAA,EAAA,KAAA,CAAC,MAAD,CAAmB,UAAW,EAAA,GAAG,EAAA,QAAO,KAAM,GAAQ,EAAA,QAAO,IAAI,EAAjE,SAAA,EACI,EAAA,EAAA,IAAA,CAAC,KAAD,CAAI,UAAW,EAAA,QAAO,OAClB,UAAA,EAAA,EAAA,KAAA,CAAC,SAAD,CACI,KAAK,SACL,GAAI,EACJ,UAAW,EAAA,QAAO,QAClB,gBAAe,EACf,gBAAe,EACf,SAAU,EAAK,SACf,YAAe,EAAO,EAAK,EAAE,EAPjC,SAAA,EASI,EAAA,EAAA,IAAA,CAAC,OAAD,CAAA,SAAO,EAAK,KAAY,CAAA,GACxB,EAAA,EAAA,IAAA,CAAC,EAAD,CAAa,UAAW,EAAA,QAAO,OAAU,CAAA,CACrC,GACR,CAAA,EACH,IACG,EAAA,EAAA,IAAA,CAAC,MAAD,CACI,GAAI,EACJ,KAAK,SACL,kBAAiB,EACjB,UAAW,EAAA,QAAO,MAEjB,SAAA,EAAK,QACL,CAAA,CAER,CAzBK,EAAA,EAAK,EAyBV,CAEb,CAAC,CACA,CAAA,CAEb,CAEA,SAAS,EAAY,CAAE,aAAqC,CACxD,OACI,EAAA,EAAA,IAAA,CAAC,MAAD,CACe,YACX,MAAM,KACN,OAAO,KACP,QAAQ,YACR,KAAK,OACL,cAAA,GAEA,UAAA,EAAA,EAAA,IAAA,CAAC,OAAD,CACI,EAAE,eACF,OAAO,eACP,YAAY,IACZ,cAAc,QACd,eAAe,OAClB,CAAA,CACA,CAAA,CAEb"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Accordion.js","names":[],"sources":["../../../src/components/Accordion/Accordion.tsx"],"sourcesContent":["import { useCallback, useId, useState } from \"react\";\nimport type { ReactNode } from \"react\";\nimport { cn } from \"@/utils/cn\";\nimport styles from \"./Accordion.module.css\";\n\nexport interface AccordionItem {\n /** Stable identifier. */\n id: string;\n title: ReactNode;\n children: ReactNode;\n disabled?: boolean;\n}\n\nexport interface AccordionProps {\n items: AccordionItem[];\n /** When `true` multiple items can be open simultaneously. Default `false`. */\n multiple?: boolean;\n /** Controlled open ids. */\n value?: string[];\n /** Uncontrolled default open ids. */\n defaultValue?: string[];\n onChange?: (openIds: string[]) => void;\n className?: string;\n}\n\n/**\n * Accessible accordion. Each item collapses/expands its content. Single-mode by\n * default — pass `multiple` to allow more than one item open at a time. Can be\n * controlled via `value` + `onChange`, or uncontrolled via `defaultValue`.\n */\nexport function Accordion({\n items,\n multiple = false,\n value,\n defaultValue = [],\n onChange,\n className,\n}: AccordionProps) {\n const reactId = useId();\n const isControlled = value !== undefined;\n const [internalOpen, setInternalOpen] = useState<string[]>(defaultValue);\n const openIds = isControlled ? value : internalOpen;\n\n const toggle = useCallback(\n (id: string): void => {\n const isOpen = openIds.includes(id);\n const next = multiple\n ? isOpen\n ? openIds.filter((x) => x !== id)\n : [...openIds, id]\n : isOpen\n ? []\n : [id];\n if (!isControlled) setInternalOpen(next);\n onChange?.(next);\n },\n [openIds, multiple, isControlled, onChange],\n );\n\n return (\n <div className={cn(styles.accordion, className)}>\n {items.map((item) => {\n const open = openIds.includes(item.id);\n const panelId = `${reactId}-${item.id}-panel`;\n const triggerId = `${reactId}-${item.id}-trigger`;\n return (\n <div key={item.id} className={cn(styles.item, open && styles.open)}>\n <h3 className={styles.header}>\n <button\n type=\"button\"\n id={triggerId}\n className={styles.trigger}\n aria-expanded={open}\n aria-controls={panelId}\n disabled={item.disabled}\n onClick={() => toggle(item.id)}\n >\n <span>{item.title}</span>\n <ChevronIcon className={styles.chevron} />\n </button>\n </h3>\n {open && (\n <div\n id={panelId}\n role=\"region\"\n aria-labelledby={triggerId}\n className={styles.panel}\n >\n {item.children}\n </div>\n )}\n </div>\n );\n })}\n </div>\n );\n}\n\nfunction ChevronIcon({ className }: { className?: string }) {\n return (\n <svg\n className={className}\n width=\"16\"\n height=\"16\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n aria-hidden\n >\n <path\n d=\"M6 9l6 6 6-6\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n />\n </svg>\n );\n}\n"],"mappings":";;;;;AA8BA,SAAgB,EAAU,EACtB,UACA,cAAW,IACX,UACA,kBAAe,CAAC,GAChB,aACA,gBACe;CACf,IAAM,IAAU,EAAM,GAChB,IAAe,MAAU,KAAA,GACzB,CAAC,GAAc,KAAmB,EAAmB,CAAY,GACjE,IAAU,IAAe,IAAQ,GAEjC,IAAS,GACV,MAAqB;EAClB,IAAM,IAAS,EAAQ,SAAS,CAAE,GAC5B,IAAO,IACP,IACI,EAAQ,QAAQ,MAAM,MAAM,CAAE,IAC9B,CAAC,GAAG,GAAS,CAAE,IACnB,IACE,CAAC,IACD,CAAC,CAAE;EAEX,AADK,KAAc,EAAgB,CAAI,GACvC,IAAW,CAAI;CACnB,GACA;EAAC;EAAS;EAAU;EAAc;CAAQ,CAC9C;CAEA,OACI,kBAAC,OAAD;EAAK,WAAW,EAAG,EAAO,WAAW,CAAS;
|
|
1
|
+
{"version":3,"file":"Accordion.js","names":[],"sources":["../../../src/components/Accordion/Accordion.tsx"],"sourcesContent":["import { useCallback, useId, useState } from \"react\";\nimport type { ReactNode } from \"react\";\nimport { cn } from \"@/utils/cn\";\nimport styles from \"./Accordion.module.css\";\n\nexport interface AccordionItem {\n /** Stable identifier. */\n id: string;\n title: ReactNode;\n children: ReactNode;\n disabled?: boolean;\n}\n\nexport interface AccordionProps {\n items: AccordionItem[];\n /** When `true` multiple items can be open simultaneously. Default `false`. */\n multiple?: boolean;\n /** Controlled open ids. */\n value?: string[];\n /** Uncontrolled default open ids. */\n defaultValue?: string[];\n onChange?: (openIds: string[]) => void;\n className?: string;\n}\n\n/**\n * Accessible accordion. Each item collapses/expands its content. Single-mode by\n * default — pass `multiple` to allow more than one item open at a time. Can be\n * controlled via `value` + `onChange`, or uncontrolled via `defaultValue`.\n */\nexport function Accordion({\n items,\n multiple = false,\n value,\n defaultValue = [],\n onChange,\n className,\n}: AccordionProps) {\n const reactId = useId();\n const isControlled = value !== undefined;\n const [internalOpen, setInternalOpen] = useState<string[]>(defaultValue);\n const openIds = isControlled ? value : internalOpen;\n\n const toggle = useCallback(\n (id: string): void => {\n const isOpen = openIds.includes(id);\n const next = multiple\n ? isOpen\n ? openIds.filter((x) => x !== id)\n : [...openIds, id]\n : isOpen\n ? []\n : [id];\n if (!isControlled) setInternalOpen(next);\n onChange?.(next);\n },\n [openIds, multiple, isControlled, onChange],\n );\n\n return (\n <div className={cn(styles.accordion, className)}>\n {items.map((item) => {\n const open = openIds.includes(item.id);\n const panelId = `${reactId}-${item.id}-panel`;\n const triggerId = `${reactId}-${item.id}-trigger`;\n return (\n <div key={item.id} className={cn(styles.item, open && styles.open)}>\n <h3 className={styles.header}>\n <button\n type=\"button\"\n id={triggerId}\n className={styles.trigger}\n aria-expanded={open}\n aria-controls={panelId}\n disabled={item.disabled}\n onClick={() => toggle(item.id)}\n >\n <span>{item.title}</span>\n <ChevronIcon className={styles.chevron} />\n </button>\n </h3>\n {open && (\n <div\n id={panelId}\n role=\"region\"\n aria-labelledby={triggerId}\n className={styles.panel}\n >\n {item.children}\n </div>\n )}\n </div>\n );\n })}\n </div>\n );\n}\n\nfunction ChevronIcon({ className }: { className?: string }) {\n return (\n <svg\n className={className}\n width=\"16\"\n height=\"16\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n aria-hidden\n >\n <path\n d=\"M6 9l6 6 6-6\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n />\n </svg>\n );\n}\n"],"mappings":";;;;;AA8BA,SAAgB,EAAU,EACtB,UACA,cAAW,IACX,UACA,kBAAe,CAAC,GAChB,aACA,gBACe;CACf,IAAM,IAAU,EAAM,GAChB,IAAe,MAAU,KAAA,GACzB,CAAC,GAAc,KAAmB,EAAmB,CAAY,GACjE,IAAU,IAAe,IAAQ,GAEjC,IAAS,GACV,MAAqB;EAClB,IAAM,IAAS,EAAQ,SAAS,CAAE,GAC5B,IAAO,IACP,IACI,EAAQ,QAAQ,MAAM,MAAM,CAAE,IAC9B,CAAC,GAAG,GAAS,CAAE,IACnB,IACE,CAAC,IACD,CAAC,CAAE;EAEX,AADK,KAAc,EAAgB,CAAI,GACvC,IAAW,CAAI;CACnB,GACA;EAAC;EAAS;EAAU;EAAc;CAAQ,CAC9C;CAEA,OACI,kBAAC,OAAD;EAAK,WAAW,EAAG,EAAO,WAAW,CAAS;EACzC,UAAA,EAAM,KAAK,MAAS;GACjB,IAAM,IAAO,EAAQ,SAAS,EAAK,EAAE,GAC/B,IAAU,GAAG,EAAQ,GAAG,EAAK,GAAG,SAChC,IAAY,GAAG,EAAQ,GAAG,EAAK,GAAG;GACxC,OACI,kBAAC,OAAD;IAAmB,WAAW,EAAG,EAAO,MAAM,KAAQ,EAAO,IAAI;IAAjE,UAAA,CACI,kBAAC,MAAD;KAAI,WAAW,EAAO;KAClB,UAAA,kBAAC,UAAD;MACI,MAAK;MACL,IAAI;MACJ,WAAW,EAAO;MAClB,iBAAe;MACf,iBAAe;MACf,UAAU,EAAK;MACf,eAAe,EAAO,EAAK,EAAE;MAPjC,UAAA,CASI,kBAAC,QAAD,EAAA,UAAO,EAAK,MAAY,CAAA,GACxB,kBAAC,GAAD,EAAa,WAAW,EAAO,QAAU,CAAA,CACrC;;IACR,CAAA,GACH,KACG,kBAAC,OAAD;KACI,IAAI;KACJ,MAAK;KACL,mBAAiB;KACjB,WAAW,EAAO;KAEjB,UAAA,EAAK;IACL,CAAA,CAER;GAzBK,GAAA,EAAK,EAyBV;EAEb,CAAC;CACA,CAAA;AAEb;AAEA,SAAS,EAAY,EAAE,gBAAqC;CACxD,OACI,kBAAC,OAAD;EACe;EACX,OAAM;EACN,QAAO;EACP,SAAQ;EACR,MAAK;EACL,eAAA;EAEA,UAAA,kBAAC,QAAD;GACI,GAAE;GACF,QAAO;GACP,aAAY;GACZ,eAAc;GACd,gBAAe;EAClB,CAAA;CACA,CAAA;AAEb"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Alert.cjs","names":[],"sources":["../../../src/components/Alert/Alert.tsx"],"sourcesContent":["import type { HTMLAttributes, ReactNode } from \"react\";\nimport { cn } from \"@/utils/cn\";\nimport styles from \"./Alert.module.css\";\n\nexport type AlertVariant = \"neutral\" | \"info\" | \"success\" | \"warning\" | \"danger\";\nexport type AlertAppearance = \"soft\" | \"solid\" | \"outline\";\n\nexport interface AlertProps extends Omit<HTMLAttributes<HTMLDivElement>, \"title\"> {\n variant?: AlertVariant;\n /** Visual style: soft (default tinted bg), solid (filled), outline (bordered). */\n appearance?: AlertAppearance;\n title?: ReactNode;\n description?: ReactNode;\n icon?: ReactNode;\n /** Show a close button and invoke this when clicked. */\n onClose?: () => void;\n /** Custom close button label for screen readers. */\n closeLabel?: string;\n}\n\n/**\n * Inline alert / notice with tone (info/success/warning/danger) and appearance\n * (soft/solid/outline). Accepts optional `icon`, `title`, `description` and\n * a dismiss button via `onClose`.\n */\nexport function Alert({\n variant = \"info\",\n appearance = \"soft\",\n title,\n description,\n icon,\n onClose,\n closeLabel = \"Dismiss\",\n className,\n children,\n ...props\n}: AlertProps) {\n return (\n <div\n role=\"alert\"\n className={cn(\n styles.alert,\n styles[variant],\n appearance === \"solid\" && styles.solid,\n appearance === \"outline\" && styles.outline,\n className,\n )}\n {...props}\n >\n {icon && <span className={styles.icon}>{icon}</span>}\n <div className={styles.content}>\n {title && <p className={styles.title}>{title}</p>}\n {description && <p className={styles.description}>{description}</p>}\n {children}\n </div>\n {onClose && (\n <button\n type=\"button\"\n className={styles.close}\n onClick={onClose}\n aria-label={closeLabel}\n >\n <CloseIcon />\n </button>\n )}\n </div>\n );\n}\n\nfunction CloseIcon() {\n return (\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" aria-hidden>\n <path\n d=\"M6 6l12 12M18 6L6 18\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n />\n </svg>\n );\n}\n"],"mappings":"yGAyBA,SAAgB,EAAM,CAClB,UAAU,OACV,aAAa,OACb,QACA,cACA,OACA,UACA,aAAa,UACb,YACA,WACA,GAAG,GACQ,CACX,OACI,EAAA,EAAA,KAAA,CAAC,MAAD,CACI,KAAK,QACL,UAAW,EAAA,GACP,EAAA,QAAO,MACP,EAAA,QAAO,GACP,IAAe,SAAW,EAAA,QAAO,MACjC,IAAe,WAAa,EAAA,QAAO,QACnC,CACJ,EACA,GAAI,
|
|
1
|
+
{"version":3,"file":"Alert.cjs","names":[],"sources":["../../../src/components/Alert/Alert.tsx"],"sourcesContent":["import type { HTMLAttributes, ReactNode } from \"react\";\nimport { cn } from \"@/utils/cn\";\nimport styles from \"./Alert.module.css\";\n\nexport type AlertVariant = \"neutral\" | \"info\" | \"success\" | \"warning\" | \"danger\";\nexport type AlertAppearance = \"soft\" | \"solid\" | \"outline\";\n\nexport interface AlertProps extends Omit<HTMLAttributes<HTMLDivElement>, \"title\"> {\n variant?: AlertVariant;\n /** Visual style: soft (default tinted bg), solid (filled), outline (bordered). */\n appearance?: AlertAppearance;\n title?: ReactNode;\n description?: ReactNode;\n icon?: ReactNode;\n /** Show a close button and invoke this when clicked. */\n onClose?: () => void;\n /** Custom close button label for screen readers. */\n closeLabel?: string;\n}\n\n/**\n * Inline alert / notice with tone (info/success/warning/danger) and appearance\n * (soft/solid/outline). Accepts optional `icon`, `title`, `description` and\n * a dismiss button via `onClose`.\n */\nexport function Alert({\n variant = \"info\",\n appearance = \"soft\",\n title,\n description,\n icon,\n onClose,\n closeLabel = \"Dismiss\",\n className,\n children,\n ...props\n}: AlertProps) {\n return (\n <div\n role=\"alert\"\n className={cn(\n styles.alert,\n styles[variant],\n appearance === \"solid\" && styles.solid,\n appearance === \"outline\" && styles.outline,\n className,\n )}\n {...props}\n >\n {icon && <span className={styles.icon}>{icon}</span>}\n <div className={styles.content}>\n {title && <p className={styles.title}>{title}</p>}\n {description && <p className={styles.description}>{description}</p>}\n {children}\n </div>\n {onClose && (\n <button\n type=\"button\"\n className={styles.close}\n onClick={onClose}\n aria-label={closeLabel}\n >\n <CloseIcon />\n </button>\n )}\n </div>\n );\n}\n\nfunction CloseIcon() {\n return (\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" aria-hidden>\n <path\n d=\"M6 6l12 12M18 6L6 18\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n />\n </svg>\n );\n}\n"],"mappings":"yGAyBA,SAAgB,EAAM,CAClB,UAAU,OACV,aAAa,OACb,QACA,cACA,OACA,UACA,aAAa,UACb,YACA,WACA,GAAG,GACQ,CACX,OACI,EAAA,EAAA,KAAA,CAAC,MAAD,CACI,KAAK,QACL,UAAW,EAAA,GACP,EAAA,QAAO,MACP,EAAA,QAAO,GACP,IAAe,SAAW,EAAA,QAAO,MACjC,IAAe,WAAa,EAAA,QAAO,QACnC,CACJ,EACA,GAAI,EATR,SAAA,CAWK,IAAQ,EAAA,EAAA,IAAA,CAAC,OAAD,CAAM,UAAW,EAAA,QAAO,KAAO,SAAA,CAAW,CAAA,GACnD,EAAA,EAAA,KAAA,CAAC,MAAD,CAAK,UAAW,EAAA,QAAO,QAAvB,SAAA,CACK,IAAS,EAAA,EAAA,IAAA,CAAC,IAAD,CAAG,UAAW,EAAA,QAAO,MAAQ,SAAA,CAAS,CAAA,EAC/C,IAAe,EAAA,EAAA,IAAA,CAAC,IAAD,CAAG,UAAW,EAAA,QAAO,YAAc,SAAA,CAAe,CAAA,EACjE,CACA,IACJ,IACG,EAAA,EAAA,IAAA,CAAC,SAAD,CACI,KAAK,SACL,UAAW,EAAA,QAAO,MAClB,QAAS,EACT,aAAY,EAEZ,UAAA,EAAA,EAAA,IAAA,CAAC,EAAD,CAAY,CAAA,CACR,CAAA,CAEX,GAEb,CAEA,SAAS,GAAY,CACjB,OACI,EAAA,EAAA,IAAA,CAAC,MAAD,CAAK,MAAM,KAAK,OAAO,KAAK,QAAQ,YAAY,KAAK,OAAO,cAAA,GACxD,UAAA,EAAA,EAAA,IAAA,CAAC,OAAD,CACI,EAAE,uBACF,OAAO,eACP,YAAY,IACZ,cAAc,OACjB,CAAA,CACA,CAAA,CAEb"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Alert.js","names":[],"sources":["../../../src/components/Alert/Alert.tsx"],"sourcesContent":["import type { HTMLAttributes, ReactNode } from \"react\";\nimport { cn } from \"@/utils/cn\";\nimport styles from \"./Alert.module.css\";\n\nexport type AlertVariant = \"neutral\" | \"info\" | \"success\" | \"warning\" | \"danger\";\nexport type AlertAppearance = \"soft\" | \"solid\" | \"outline\";\n\nexport interface AlertProps extends Omit<HTMLAttributes<HTMLDivElement>, \"title\"> {\n variant?: AlertVariant;\n /** Visual style: soft (default tinted bg), solid (filled), outline (bordered). */\n appearance?: AlertAppearance;\n title?: ReactNode;\n description?: ReactNode;\n icon?: ReactNode;\n /** Show a close button and invoke this when clicked. */\n onClose?: () => void;\n /** Custom close button label for screen readers. */\n closeLabel?: string;\n}\n\n/**\n * Inline alert / notice with tone (info/success/warning/danger) and appearance\n * (soft/solid/outline). Accepts optional `icon`, `title`, `description` and\n * a dismiss button via `onClose`.\n */\nexport function Alert({\n variant = \"info\",\n appearance = \"soft\",\n title,\n description,\n icon,\n onClose,\n closeLabel = \"Dismiss\",\n className,\n children,\n ...props\n}: AlertProps) {\n return (\n <div\n role=\"alert\"\n className={cn(\n styles.alert,\n styles[variant],\n appearance === \"solid\" && styles.solid,\n appearance === \"outline\" && styles.outline,\n className,\n )}\n {...props}\n >\n {icon && <span className={styles.icon}>{icon}</span>}\n <div className={styles.content}>\n {title && <p className={styles.title}>{title}</p>}\n {description && <p className={styles.description}>{description}</p>}\n {children}\n </div>\n {onClose && (\n <button\n type=\"button\"\n className={styles.close}\n onClick={onClose}\n aria-label={closeLabel}\n >\n <CloseIcon />\n </button>\n )}\n </div>\n );\n}\n\nfunction CloseIcon() {\n return (\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" aria-hidden>\n <path\n d=\"M6 6l12 12M18 6L6 18\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n />\n </svg>\n );\n}\n"],"mappings":";;;;AAyBA,SAAgB,EAAM,EAClB,aAAU,QACV,gBAAa,QACb,UACA,gBACA,SACA,YACA,gBAAa,WACb,cACA,aACA,GAAG,KACQ;CACX,OACI,kBAAC,OAAD;EACI,MAAK;EACL,WAAW,EACP,EAAO,OACP,EAAO,IACP,MAAe,WAAW,EAAO,OACjC,MAAe,aAAa,EAAO,SACnC,CACJ;EACA,GAAI;
|
|
1
|
+
{"version":3,"file":"Alert.js","names":[],"sources":["../../../src/components/Alert/Alert.tsx"],"sourcesContent":["import type { HTMLAttributes, ReactNode } from \"react\";\nimport { cn } from \"@/utils/cn\";\nimport styles from \"./Alert.module.css\";\n\nexport type AlertVariant = \"neutral\" | \"info\" | \"success\" | \"warning\" | \"danger\";\nexport type AlertAppearance = \"soft\" | \"solid\" | \"outline\";\n\nexport interface AlertProps extends Omit<HTMLAttributes<HTMLDivElement>, \"title\"> {\n variant?: AlertVariant;\n /** Visual style: soft (default tinted bg), solid (filled), outline (bordered). */\n appearance?: AlertAppearance;\n title?: ReactNode;\n description?: ReactNode;\n icon?: ReactNode;\n /** Show a close button and invoke this when clicked. */\n onClose?: () => void;\n /** Custom close button label for screen readers. */\n closeLabel?: string;\n}\n\n/**\n * Inline alert / notice with tone (info/success/warning/danger) and appearance\n * (soft/solid/outline). Accepts optional `icon`, `title`, `description` and\n * a dismiss button via `onClose`.\n */\nexport function Alert({\n variant = \"info\",\n appearance = \"soft\",\n title,\n description,\n icon,\n onClose,\n closeLabel = \"Dismiss\",\n className,\n children,\n ...props\n}: AlertProps) {\n return (\n <div\n role=\"alert\"\n className={cn(\n styles.alert,\n styles[variant],\n appearance === \"solid\" && styles.solid,\n appearance === \"outline\" && styles.outline,\n className,\n )}\n {...props}\n >\n {icon && <span className={styles.icon}>{icon}</span>}\n <div className={styles.content}>\n {title && <p className={styles.title}>{title}</p>}\n {description && <p className={styles.description}>{description}</p>}\n {children}\n </div>\n {onClose && (\n <button\n type=\"button\"\n className={styles.close}\n onClick={onClose}\n aria-label={closeLabel}\n >\n <CloseIcon />\n </button>\n )}\n </div>\n );\n}\n\nfunction CloseIcon() {\n return (\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" aria-hidden>\n <path\n d=\"M6 6l12 12M18 6L6 18\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n />\n </svg>\n );\n}\n"],"mappings":";;;;AAyBA,SAAgB,EAAM,EAClB,aAAU,QACV,gBAAa,QACb,UACA,gBACA,SACA,YACA,gBAAa,WACb,cACA,aACA,GAAG,KACQ;CACX,OACI,kBAAC,OAAD;EACI,MAAK;EACL,WAAW,EACP,EAAO,OACP,EAAO,IACP,MAAe,WAAW,EAAO,OACjC,MAAe,aAAa,EAAO,SACnC,CACJ;EACA,GAAI;EATR,UAAA;GAWK,KAAQ,kBAAC,QAAD;IAAM,WAAW,EAAO;IAAO,UAAA;GAAW,CAAA;GACnD,kBAAC,OAAD;IAAK,WAAW,EAAO;IAAvB,UAAA;KACK,KAAS,kBAAC,KAAD;MAAG,WAAW,EAAO;MAAQ,UAAA;KAAS,CAAA;KAC/C,KAAe,kBAAC,KAAD;MAAG,WAAW,EAAO;MAAc,UAAA;KAAe,CAAA;KACjE;IACA;;GACJ,KACG,kBAAC,UAAD;IACI,MAAK;IACL,WAAW,EAAO;IAClB,SAAS;IACT,cAAY;IAEZ,UAAA,kBAAC,GAAD,CAAY,CAAA;GACR,CAAA;EAEX;;AAEb;AAEA,SAAS,IAAY;CACjB,OACI,kBAAC,OAAD;EAAK,OAAM;EAAK,QAAO;EAAK,SAAQ;EAAY,MAAK;EAAO,eAAA;EACxD,UAAA,kBAAC,QAAD;GACI,GAAE;GACF,QAAO;GACP,aAAY;GACZ,eAAc;EACjB,CAAA;CACA,CAAA;AAEb"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AppBar.cjs","names":[],"sources":["../../../src/components/AppBar/AppBar.tsx"],"sourcesContent":["/**\n * @tempest-limits props-count — a top bar is a slot layout: leading (leading,\n * showBack, onBack, backLabel, backIcon, brand), centre (title, centered) and\n * trailing (actions), plus the chrome it sits in (sticky, tone, bordered, safeArea).\n * Every one of those is a real decision an app makes per screen, and the alternative\n * to accepting them is the app rebuilding the bar around the parts it wanted to\n * change.\n */\nimport type { HTMLAttributes, ReactNode } from \"react\";\nimport { ArrowLeft } from \"lucide-react\";\nimport { cn } from \"@/utils/cn\";\nimport styles from \"./AppBar.module.css\";\n\nexport type AppBarTone = \"surface\" | \"primary\" | \"transparent\";\n\nexport interface AppBarProps extends Omit<HTMLAttributes<HTMLElement>, \"title\"> {\n /** Page title — string or any node. Rendered as the bar's `<h1>`. */\n title?: ReactNode;\n /**\n * Replace the whole left slot. When set, the auto back button and `brand`\n * are ignored — you own the leading content.\n */\n leading?: ReactNode;\n /** Show a back button in the left slot. Ignored when `leading` is set. */\n showBack?: boolean;\n /**\n * Back-button handler. Defaults to `window.history.back()`. With a router,\n * pass `onBack={() => navigate(-1)}`.\n */\n onBack?: () => void;\n /** Accessible label for the back button. Default `\"Go back\"`. */\n backLabel?: string;\n /** Custom back icon. Default a left arrow. */\n backIcon?: ReactNode;\n /** Brand / logo node shown in the left slot (after the back button). */\n brand?: ReactNode;\n /** Right slot — action buttons / menu. One node or many. */\n actions?: ReactNode;\n /** Center the title (three-column grid). Default `false` (left-aligned). */\n centered?: boolean;\n /** Stick to the top of the scroll container. Default `true`. */\n sticky?: boolean;\n /** Visual tone. Default `\"surface\"`. */\n tone?: AppBarTone;\n /** Thin bottom border. Default `true`. */\n bordered?: boolean;\n /** Add `env(safe-area-inset-top)` padding (iOS notch / PWA). Default `true`. */\n safeArea?: boolean;\n}\n\n/**\n * Mobile-first top app bar for PWAs — leading (back / brand) + title +\n * trailing actions, sticky with safe-area padding out of the box.\n *\n * Consumers customise via tokens (`--tempest-*`) and slots; the SDK ships the\n * layout, sticky/safe-area behaviour, and accessible back button so apps don't\n * hand-roll one per screen. For a desktop three-slot nav use [[Navbar]].\n *\n * @example\n * // Detail screen with back + a trailing action\n * <AppBar\n * title=\"Profile\"\n * showBack\n * onBack={() => navigate(-1)}\n * actions={<IconButton icon={<Settings />} onClick={openSettings} />}\n * />\n *\n * @example\n * // Home screen — brand left, centered title disabled\n * <AppBar brand={<Logo />} actions={<UserMenu />} />\n */\nexport function AppBar({\n title,\n leading,\n showBack = false,\n onBack,\n backLabel = \"Go back\",\n backIcon,\n brand,\n actions,\n centered = false,\n sticky = true,\n tone = \"surface\",\n bordered = true,\n safeArea = true,\n className,\n ...props\n}: AppBarProps) {\n const handleBack = onBack ?? (() => window.history.back());\n\n const leadingContent = leading ?? (\n <>\n {showBack && (\n <button\n type=\"button\"\n className={styles.iconBtn}\n onClick={handleBack}\n aria-label={backLabel}\n >\n {backIcon ?? <ArrowLeft size={22} aria-hidden=\"true\" />}\n </button>\n )}\n {brand && <span className={styles.brand}>{brand}</span>}\n </>\n );\n\n return (\n <header\n className={cn(\n styles.bar,\n styles[tone],\n centered && styles.centered,\n sticky && styles.sticky,\n bordered && styles.bordered,\n safeArea && styles.safeArea,\n className,\n )}\n {...props}\n >\n <div className={styles.leading}>{leadingContent}</div>\n {title != null && title !== \"\" && (\n <h1 className={styles.title} aria-live=\"polite\">\n {title}\n </h1>\n )}\n <div className={styles.actions}>{actions}</div>\n </header>\n );\n}\n"],"mappings":"oIAuEA,SAAgB,EAAO,CACnB,QACA,UACA,WAAW,GACX,SACA,YAAY,UACZ,WACA,QACA,UACA,WAAW,GACX,SAAS,GACT,OAAO,UACP,WAAW,GACX,WAAW,GACX,YACA,GAAG,GACS,CACZ,IAAM,EAAa,QAAiB,OAAO,QAAQ,KAAK,GAElD,EAAiB,IACnB,EAAA,EAAA,KAAA,CAAA,EAAA,SAAA,CAAA,SAAA,CACK,IACG,EAAA,EAAA,IAAA,CAAC,SAAD,CACI,KAAK,SACL,UAAW,EAAA,QAAO,QAClB,QAAS,EACT,aAAY,
|
|
1
|
+
{"version":3,"file":"AppBar.cjs","names":[],"sources":["../../../src/components/AppBar/AppBar.tsx"],"sourcesContent":["/**\n * @tempest-limits props-count — a top bar is a slot layout: leading (leading,\n * showBack, onBack, backLabel, backIcon, brand), centre (title, centered) and\n * trailing (actions), plus the chrome it sits in (sticky, tone, bordered, safeArea).\n * Every one of those is a real decision an app makes per screen, and the alternative\n * to accepting them is the app rebuilding the bar around the parts it wanted to\n * change.\n */\nimport type { HTMLAttributes, ReactNode } from \"react\";\nimport { ArrowLeft } from \"lucide-react\";\nimport { cn } from \"@/utils/cn\";\nimport styles from \"./AppBar.module.css\";\n\nexport type AppBarTone = \"surface\" | \"primary\" | \"transparent\";\n\nexport interface AppBarProps extends Omit<HTMLAttributes<HTMLElement>, \"title\"> {\n /** Page title — string or any node. Rendered as the bar's `<h1>`. */\n title?: ReactNode;\n /**\n * Replace the whole left slot. When set, the auto back button and `brand`\n * are ignored — you own the leading content.\n */\n leading?: ReactNode;\n /** Show a back button in the left slot. Ignored when `leading` is set. */\n showBack?: boolean;\n /**\n * Back-button handler. Defaults to `window.history.back()`. With a router,\n * pass `onBack={() => navigate(-1)}`.\n */\n onBack?: () => void;\n /** Accessible label for the back button. Default `\"Go back\"`. */\n backLabel?: string;\n /** Custom back icon. Default a left arrow. */\n backIcon?: ReactNode;\n /** Brand / logo node shown in the left slot (after the back button). */\n brand?: ReactNode;\n /** Right slot — action buttons / menu. One node or many. */\n actions?: ReactNode;\n /** Center the title (three-column grid). Default `false` (left-aligned). */\n centered?: boolean;\n /** Stick to the top of the scroll container. Default `true`. */\n sticky?: boolean;\n /** Visual tone. Default `\"surface\"`. */\n tone?: AppBarTone;\n /** Thin bottom border. Default `true`. */\n bordered?: boolean;\n /** Add `env(safe-area-inset-top)` padding (iOS notch / PWA). Default `true`. */\n safeArea?: boolean;\n}\n\n/**\n * Mobile-first top app bar for PWAs — leading (back / brand) + title +\n * trailing actions, sticky with safe-area padding out of the box.\n *\n * Consumers customise via tokens (`--tempest-*`) and slots; the SDK ships the\n * layout, sticky/safe-area behaviour, and accessible back button so apps don't\n * hand-roll one per screen. For a desktop three-slot nav use [[Navbar]].\n *\n * @example\n * // Detail screen with back + a trailing action\n * <AppBar\n * title=\"Profile\"\n * showBack\n * onBack={() => navigate(-1)}\n * actions={<IconButton icon={<Settings />} onClick={openSettings} />}\n * />\n *\n * @example\n * // Home screen — brand left, centered title disabled\n * <AppBar brand={<Logo />} actions={<UserMenu />} />\n */\nexport function AppBar({\n title,\n leading,\n showBack = false,\n onBack,\n backLabel = \"Go back\",\n backIcon,\n brand,\n actions,\n centered = false,\n sticky = true,\n tone = \"surface\",\n bordered = true,\n safeArea = true,\n className,\n ...props\n}: AppBarProps) {\n const handleBack = onBack ?? (() => window.history.back());\n\n const leadingContent = leading ?? (\n <>\n {showBack && (\n <button\n type=\"button\"\n className={styles.iconBtn}\n onClick={handleBack}\n aria-label={backLabel}\n >\n {backIcon ?? <ArrowLeft size={22} aria-hidden=\"true\" />}\n </button>\n )}\n {brand && <span className={styles.brand}>{brand}</span>}\n </>\n );\n\n return (\n <header\n className={cn(\n styles.bar,\n styles[tone],\n centered && styles.centered,\n sticky && styles.sticky,\n bordered && styles.bordered,\n safeArea && styles.safeArea,\n className,\n )}\n {...props}\n >\n <div className={styles.leading}>{leadingContent}</div>\n {title != null && title !== \"\" && (\n <h1 className={styles.title} aria-live=\"polite\">\n {title}\n </h1>\n )}\n <div className={styles.actions}>{actions}</div>\n </header>\n );\n}\n"],"mappings":"oIAuEA,SAAgB,EAAO,CACnB,QACA,UACA,WAAW,GACX,SACA,YAAY,UACZ,WACA,QACA,UACA,WAAW,GACX,SAAS,GACT,OAAO,UACP,WAAW,GACX,WAAW,GACX,YACA,GAAG,GACS,CACZ,IAAM,EAAa,QAAiB,OAAO,QAAQ,KAAK,GAElD,EAAiB,IACnB,EAAA,EAAA,KAAA,CAAA,EAAA,SAAA,CAAA,SAAA,CACK,IACG,EAAA,EAAA,IAAA,CAAC,SAAD,CACI,KAAK,SACL,UAAW,EAAA,QAAO,QAClB,QAAS,EACT,aAAY,EAEX,SAAA,IAAY,EAAA,EAAA,IAAA,CAAC,EAAA,UAAD,CAAW,KAAM,GAAI,cAAY,MAAQ,CAAA,CAClD,CAAA,EAEX,IAAS,EAAA,EAAA,IAAA,CAAC,OAAD,CAAM,UAAW,EAAA,QAAO,MAAQ,SAAA,CAAY,CAAA,CACxD,CAAA,CAAA,EAGN,OACI,EAAA,EAAA,KAAA,CAAC,SAAD,CACI,UAAW,EAAA,GACP,EAAA,QAAO,IACP,EAAA,QAAO,GACP,GAAY,EAAA,QAAO,SACnB,GAAU,EAAA,QAAO,OACjB,GAAY,EAAA,QAAO,SACnB,GAAY,EAAA,QAAO,SACnB,CACJ,EACA,GAAI,EAVR,SAAA,EAYI,EAAA,EAAA,IAAA,CAAC,MAAD,CAAK,UAAW,EAAA,QAAO,QAAU,SAAA,CAAoB,CAAA,EACpD,GAAS,MAAQ,IAAU,KACxB,EAAA,EAAA,IAAA,CAAC,KAAD,CAAI,UAAW,EAAA,QAAO,MAAO,YAAU,SAClC,SAAA,CACD,CAAA,GAER,EAAA,EAAA,IAAA,CAAC,MAAD,CAAK,UAAW,EAAA,QAAO,QAAU,SAAA,CAAa,CAAA,CAC1C,GAEhB"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AppBar.js","names":[],"sources":["../../../src/components/AppBar/AppBar.tsx"],"sourcesContent":["/**\n * @tempest-limits props-count — a top bar is a slot layout: leading (leading,\n * showBack, onBack, backLabel, backIcon, brand), centre (title, centered) and\n * trailing (actions), plus the chrome it sits in (sticky, tone, bordered, safeArea).\n * Every one of those is a real decision an app makes per screen, and the alternative\n * to accepting them is the app rebuilding the bar around the parts it wanted to\n * change.\n */\nimport type { HTMLAttributes, ReactNode } from \"react\";\nimport { ArrowLeft } from \"lucide-react\";\nimport { cn } from \"@/utils/cn\";\nimport styles from \"./AppBar.module.css\";\n\nexport type AppBarTone = \"surface\" | \"primary\" | \"transparent\";\n\nexport interface AppBarProps extends Omit<HTMLAttributes<HTMLElement>, \"title\"> {\n /** Page title — string or any node. Rendered as the bar's `<h1>`. */\n title?: ReactNode;\n /**\n * Replace the whole left slot. When set, the auto back button and `brand`\n * are ignored — you own the leading content.\n */\n leading?: ReactNode;\n /** Show a back button in the left slot. Ignored when `leading` is set. */\n showBack?: boolean;\n /**\n * Back-button handler. Defaults to `window.history.back()`. With a router,\n * pass `onBack={() => navigate(-1)}`.\n */\n onBack?: () => void;\n /** Accessible label for the back button. Default `\"Go back\"`. */\n backLabel?: string;\n /** Custom back icon. Default a left arrow. */\n backIcon?: ReactNode;\n /** Brand / logo node shown in the left slot (after the back button). */\n brand?: ReactNode;\n /** Right slot — action buttons / menu. One node or many. */\n actions?: ReactNode;\n /** Center the title (three-column grid). Default `false` (left-aligned). */\n centered?: boolean;\n /** Stick to the top of the scroll container. Default `true`. */\n sticky?: boolean;\n /** Visual tone. Default `\"surface\"`. */\n tone?: AppBarTone;\n /** Thin bottom border. Default `true`. */\n bordered?: boolean;\n /** Add `env(safe-area-inset-top)` padding (iOS notch / PWA). Default `true`. */\n safeArea?: boolean;\n}\n\n/**\n * Mobile-first top app bar for PWAs — leading (back / brand) + title +\n * trailing actions, sticky with safe-area padding out of the box.\n *\n * Consumers customise via tokens (`--tempest-*`) and slots; the SDK ships the\n * layout, sticky/safe-area behaviour, and accessible back button so apps don't\n * hand-roll one per screen. For a desktop three-slot nav use [[Navbar]].\n *\n * @example\n * // Detail screen with back + a trailing action\n * <AppBar\n * title=\"Profile\"\n * showBack\n * onBack={() => navigate(-1)}\n * actions={<IconButton icon={<Settings />} onClick={openSettings} />}\n * />\n *\n * @example\n * // Home screen — brand left, centered title disabled\n * <AppBar brand={<Logo />} actions={<UserMenu />} />\n */\nexport function AppBar({\n title,\n leading,\n showBack = false,\n onBack,\n backLabel = \"Go back\",\n backIcon,\n brand,\n actions,\n centered = false,\n sticky = true,\n tone = \"surface\",\n bordered = true,\n safeArea = true,\n className,\n ...props\n}: AppBarProps) {\n const handleBack = onBack ?? (() => window.history.back());\n\n const leadingContent = leading ?? (\n <>\n {showBack && (\n <button\n type=\"button\"\n className={styles.iconBtn}\n onClick={handleBack}\n aria-label={backLabel}\n >\n {backIcon ?? <ArrowLeft size={22} aria-hidden=\"true\" />}\n </button>\n )}\n {brand && <span className={styles.brand}>{brand}</span>}\n </>\n );\n\n return (\n <header\n className={cn(\n styles.bar,\n styles[tone],\n centered && styles.centered,\n sticky && styles.sticky,\n bordered && styles.bordered,\n safeArea && styles.safeArea,\n className,\n )}\n {...props}\n >\n <div className={styles.leading}>{leadingContent}</div>\n {title != null && title !== \"\" && (\n <h1 className={styles.title} aria-live=\"polite\">\n {title}\n </h1>\n )}\n <div className={styles.actions}>{actions}</div>\n </header>\n );\n}\n"],"mappings":";;;;;AAuEA,SAAgB,EAAO,EACnB,UACA,YACA,cAAW,IACX,WACA,eAAY,WACZ,aACA,UACA,YACA,cAAW,IACX,YAAS,IACT,UAAO,WACP,cAAW,IACX,cAAW,IACX,cACA,GAAG,KACS;CACZ,IAAM,IAAa,YAAiB,OAAO,QAAQ,KAAK,IAElD,IAAiB,KACnB,kBAAA,GAAA,EAAA,UAAA,CACK,KACG,kBAAC,UAAD;EACI,MAAK;EACL,WAAW,EAAO;EAClB,SAAS;EACT,cAAY;
|
|
1
|
+
{"version":3,"file":"AppBar.js","names":[],"sources":["../../../src/components/AppBar/AppBar.tsx"],"sourcesContent":["/**\n * @tempest-limits props-count — a top bar is a slot layout: leading (leading,\n * showBack, onBack, backLabel, backIcon, brand), centre (title, centered) and\n * trailing (actions), plus the chrome it sits in (sticky, tone, bordered, safeArea).\n * Every one of those is a real decision an app makes per screen, and the alternative\n * to accepting them is the app rebuilding the bar around the parts it wanted to\n * change.\n */\nimport type { HTMLAttributes, ReactNode } from \"react\";\nimport { ArrowLeft } from \"lucide-react\";\nimport { cn } from \"@/utils/cn\";\nimport styles from \"./AppBar.module.css\";\n\nexport type AppBarTone = \"surface\" | \"primary\" | \"transparent\";\n\nexport interface AppBarProps extends Omit<HTMLAttributes<HTMLElement>, \"title\"> {\n /** Page title — string or any node. Rendered as the bar's `<h1>`. */\n title?: ReactNode;\n /**\n * Replace the whole left slot. When set, the auto back button and `brand`\n * are ignored — you own the leading content.\n */\n leading?: ReactNode;\n /** Show a back button in the left slot. Ignored when `leading` is set. */\n showBack?: boolean;\n /**\n * Back-button handler. Defaults to `window.history.back()`. With a router,\n * pass `onBack={() => navigate(-1)}`.\n */\n onBack?: () => void;\n /** Accessible label for the back button. Default `\"Go back\"`. */\n backLabel?: string;\n /** Custom back icon. Default a left arrow. */\n backIcon?: ReactNode;\n /** Brand / logo node shown in the left slot (after the back button). */\n brand?: ReactNode;\n /** Right slot — action buttons / menu. One node or many. */\n actions?: ReactNode;\n /** Center the title (three-column grid). Default `false` (left-aligned). */\n centered?: boolean;\n /** Stick to the top of the scroll container. Default `true`. */\n sticky?: boolean;\n /** Visual tone. Default `\"surface\"`. */\n tone?: AppBarTone;\n /** Thin bottom border. Default `true`. */\n bordered?: boolean;\n /** Add `env(safe-area-inset-top)` padding (iOS notch / PWA). Default `true`. */\n safeArea?: boolean;\n}\n\n/**\n * Mobile-first top app bar for PWAs — leading (back / brand) + title +\n * trailing actions, sticky with safe-area padding out of the box.\n *\n * Consumers customise via tokens (`--tempest-*`) and slots; the SDK ships the\n * layout, sticky/safe-area behaviour, and accessible back button so apps don't\n * hand-roll one per screen. For a desktop three-slot nav use [[Navbar]].\n *\n * @example\n * // Detail screen with back + a trailing action\n * <AppBar\n * title=\"Profile\"\n * showBack\n * onBack={() => navigate(-1)}\n * actions={<IconButton icon={<Settings />} onClick={openSettings} />}\n * />\n *\n * @example\n * // Home screen — brand left, centered title disabled\n * <AppBar brand={<Logo />} actions={<UserMenu />} />\n */\nexport function AppBar({\n title,\n leading,\n showBack = false,\n onBack,\n backLabel = \"Go back\",\n backIcon,\n brand,\n actions,\n centered = false,\n sticky = true,\n tone = \"surface\",\n bordered = true,\n safeArea = true,\n className,\n ...props\n}: AppBarProps) {\n const handleBack = onBack ?? (() => window.history.back());\n\n const leadingContent = leading ?? (\n <>\n {showBack && (\n <button\n type=\"button\"\n className={styles.iconBtn}\n onClick={handleBack}\n aria-label={backLabel}\n >\n {backIcon ?? <ArrowLeft size={22} aria-hidden=\"true\" />}\n </button>\n )}\n {brand && <span className={styles.brand}>{brand}</span>}\n </>\n );\n\n return (\n <header\n className={cn(\n styles.bar,\n styles[tone],\n centered && styles.centered,\n sticky && styles.sticky,\n bordered && styles.bordered,\n safeArea && styles.safeArea,\n className,\n )}\n {...props}\n >\n <div className={styles.leading}>{leadingContent}</div>\n {title != null && title !== \"\" && (\n <h1 className={styles.title} aria-live=\"polite\">\n {title}\n </h1>\n )}\n <div className={styles.actions}>{actions}</div>\n </header>\n );\n}\n"],"mappings":";;;;;AAuEA,SAAgB,EAAO,EACnB,UACA,YACA,cAAW,IACX,WACA,eAAY,WACZ,aACA,UACA,YACA,cAAW,IACX,YAAS,IACT,UAAO,WACP,cAAW,IACX,cAAW,IACX,cACA,GAAG,KACS;CACZ,IAAM,IAAa,YAAiB,OAAO,QAAQ,KAAK,IAElD,IAAiB,KACnB,kBAAA,GAAA,EAAA,UAAA,CACK,KACG,kBAAC,UAAD;EACI,MAAK;EACL,WAAW,EAAO;EAClB,SAAS;EACT,cAAY;EAEX,UAAA,KAAY,kBAAC,GAAD;GAAW,MAAM;GAAI,eAAY;EAAQ,CAAA;CAClD,CAAA,GAEX,KAAS,kBAAC,QAAD;EAAM,WAAW,EAAO;EAAQ,UAAA;CAAY,CAAA,CACxD,EAAA,CAAA;CAGN,OACI,kBAAC,UAAD;EACI,WAAW,EACP,EAAO,KACP,EAAO,IACP,KAAY,EAAO,UACnB,KAAU,EAAO,QACjB,KAAY,EAAO,UACnB,KAAY,EAAO,UACnB,CACJ;EACA,GAAI;EAVR,UAAA;GAYI,kBAAC,OAAD;IAAK,WAAW,EAAO;IAAU,UAAA;GAAoB,CAAA;GACpD,KAAS,QAAQ,MAAU,MACxB,kBAAC,MAAD;IAAI,WAAW,EAAO;IAAO,aAAU;IAClC,UAAA;GACD,CAAA;GAER,kBAAC,OAAD;IAAK,WAAW,EAAO;IAAU,UAAA;GAAa,CAAA;EAC1C;;AAEhB"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AppShell.cjs","names":[],"sources":["../../../src/components/AppShell/AppShell.tsx"],"sourcesContent":["import type { HTMLAttributes, ReactNode } from \"react\";\nimport { cn } from \"@/utils/cn\";\nimport { useBreakpoint } from \"@/hooks/use-breakpoint\";\nimport styles from \"./AppShell.module.css\";\n\nexport interface AppShellProps extends HTMLAttributes<HTMLDivElement> {\n /** Top navigation bar (Navbar / custom). Renders on every breakpoint. */\n navbar?: ReactNode;\n /** Side navigation (Sidebar). Hidden below `md` — pair with `<Drawer>` for mobile. */\n sidebar?: ReactNode;\n /** Mobile-only bottom navigation (BottomNavigation). Hidden at `md+`. */\n bottomNav?: ReactNode;\n /** Footer rendered after the main content. */\n footer?: ReactNode;\n /** Breakpoint at which the sidebar appears. Default `\"md\"`. */\n sidebarBreakpoint?: \"sm\" | \"md\" | \"lg\" | \"xl\";\n children?: ReactNode;\n}\n\n/**\n * Full app layout — composes [[Navbar]] + [[Sidebar]] + content +\n * [[BottomNavigation]] + footer with responsive behaviour:\n *\n * - **Desktop (`>= sidebarBreakpoint`)**: navbar + sidebar + main + footer.\n * - **Mobile (`< sidebarBreakpoint`)**: navbar + main + bottom nav + footer\n * (sidebar hidden — app should expose it via `<Drawer>` from a hamburger).\n *\n * @example\n * <AppShell\n * navbar={<Navbar logo={<Brand />} actions={<UserMenu />} />}\n * sidebar={<Sidebar items={...} value={tab} onChange={setTab} />}\n * bottomNav={<BottomNavigation items={...} value={tab} onChange={setTab} />}\n * footer={<Footer />}\n * >\n * <Page title=\"Dashboard\">{content}</Page>\n * </AppShell>\n */\nexport function AppShell({\n navbar,\n sidebar,\n bottomNav,\n footer,\n sidebarBreakpoint = \"md\",\n className,\n children,\n ...props\n}: AppShellProps) {\n const bp = useBreakpoint();\n const showSidebar = bp.above(sidebarBreakpoint);\n return (\n <div className={cn(styles.shell, className)} {...props}>\n {navbar && <div className={styles.navbar}>{navbar}</div>}\n <div className={styles.row}>\n {sidebar && showSidebar && <div className={styles.sidebar}>{sidebar}</div>}\n <main className={styles.main}>{children}</main>\n </div>\n {footer && <div className={styles.footer}>{footer}</div>}\n {bottomNav && !showSidebar && <div className={styles.bottomNav}>{bottomNav}</div>}\n </div>\n );\n}\n"],"mappings":"wJAqCA,SAAgB,EAAS,CACrB,SACA,UACA,YACA,SACA,oBAAoB,KACpB,YACA,WACA,GAAG,GACW,CAEd,IAAM,EADK,EAAA,cACS,CAAA,CAAG,MAAM,CAAiB,EAC9C,OACI,EAAA,EAAA,KAAA,CAAC,MAAD,CAAK,UAAW,EAAA,GAAG,EAAA,QAAO,MAAO,CAAS,EAAG,GAAI,
|
|
1
|
+
{"version":3,"file":"AppShell.cjs","names":[],"sources":["../../../src/components/AppShell/AppShell.tsx"],"sourcesContent":["import type { HTMLAttributes, ReactNode } from \"react\";\nimport { cn } from \"@/utils/cn\";\nimport { useBreakpoint } from \"@/hooks/use-breakpoint\";\nimport styles from \"./AppShell.module.css\";\n\nexport interface AppShellProps extends HTMLAttributes<HTMLDivElement> {\n /** Top navigation bar (Navbar / custom). Renders on every breakpoint. */\n navbar?: ReactNode;\n /** Side navigation (Sidebar). Hidden below `md` — pair with `<Drawer>` for mobile. */\n sidebar?: ReactNode;\n /** Mobile-only bottom navigation (BottomNavigation). Hidden at `md+`. */\n bottomNav?: ReactNode;\n /** Footer rendered after the main content. */\n footer?: ReactNode;\n /** Breakpoint at which the sidebar appears. Default `\"md\"`. */\n sidebarBreakpoint?: \"sm\" | \"md\" | \"lg\" | \"xl\";\n children?: ReactNode;\n}\n\n/**\n * Full app layout — composes [[Navbar]] + [[Sidebar]] + content +\n * [[BottomNavigation]] + footer with responsive behaviour:\n *\n * - **Desktop (`>= sidebarBreakpoint`)**: navbar + sidebar + main + footer.\n * - **Mobile (`< sidebarBreakpoint`)**: navbar + main + bottom nav + footer\n * (sidebar hidden — app should expose it via `<Drawer>` from a hamburger).\n *\n * @example\n * <AppShell\n * navbar={<Navbar logo={<Brand />} actions={<UserMenu />} />}\n * sidebar={<Sidebar items={...} value={tab} onChange={setTab} />}\n * bottomNav={<BottomNavigation items={...} value={tab} onChange={setTab} />}\n * footer={<Footer />}\n * >\n * <Page title=\"Dashboard\">{content}</Page>\n * </AppShell>\n */\nexport function AppShell({\n navbar,\n sidebar,\n bottomNav,\n footer,\n sidebarBreakpoint = \"md\",\n className,\n children,\n ...props\n}: AppShellProps) {\n const bp = useBreakpoint();\n const showSidebar = bp.above(sidebarBreakpoint);\n return (\n <div className={cn(styles.shell, className)} {...props}>\n {navbar && <div className={styles.navbar}>{navbar}</div>}\n <div className={styles.row}>\n {sidebar && showSidebar && <div className={styles.sidebar}>{sidebar}</div>}\n <main className={styles.main}>{children}</main>\n </div>\n {footer && <div className={styles.footer}>{footer}</div>}\n {bottomNav && !showSidebar && <div className={styles.bottomNav}>{bottomNav}</div>}\n </div>\n );\n}\n"],"mappings":"wJAqCA,SAAgB,EAAS,CACrB,SACA,UACA,YACA,SACA,oBAAoB,KACpB,YACA,WACA,GAAG,GACW,CAEd,IAAM,EADK,EAAA,cACS,CAAA,CAAG,MAAM,CAAiB,EAC9C,OACI,EAAA,EAAA,KAAA,CAAC,MAAD,CAAK,UAAW,EAAA,GAAG,EAAA,QAAO,MAAO,CAAS,EAAG,GAAI,EAAjD,SAAA,CACK,IAAU,EAAA,EAAA,IAAA,CAAC,MAAD,CAAK,UAAW,EAAA,QAAO,OAAS,SAAA,CAAY,CAAA,GACvD,EAAA,EAAA,KAAA,CAAC,MAAD,CAAK,UAAW,EAAA,QAAO,IAAvB,SAAA,CACK,GAAW,IAAe,EAAA,EAAA,IAAA,CAAC,MAAD,CAAK,UAAW,EAAA,QAAO,QAAU,SAAA,CAAa,CAAA,GACzE,EAAA,EAAA,IAAA,CAAC,OAAD,CAAM,UAAW,EAAA,QAAO,KAAO,UAAe,CAAA,CAC7C,IACJ,IAAU,EAAA,EAAA,IAAA,CAAC,MAAD,CAAK,UAAW,EAAA,QAAO,OAAS,SAAA,CAAY,CAAA,EACtD,GAAa,CAAC,IAAe,EAAA,EAAA,IAAA,CAAC,MAAD,CAAK,UAAW,EAAA,QAAO,UAAY,SAAA,CAAe,CAAA,CAC/E,GAEb"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AppShell.js","names":[],"sources":["../../../src/components/AppShell/AppShell.tsx"],"sourcesContent":["import type { HTMLAttributes, ReactNode } from \"react\";\nimport { cn } from \"@/utils/cn\";\nimport { useBreakpoint } from \"@/hooks/use-breakpoint\";\nimport styles from \"./AppShell.module.css\";\n\nexport interface AppShellProps extends HTMLAttributes<HTMLDivElement> {\n /** Top navigation bar (Navbar / custom). Renders on every breakpoint. */\n navbar?: ReactNode;\n /** Side navigation (Sidebar). Hidden below `md` — pair with `<Drawer>` for mobile. */\n sidebar?: ReactNode;\n /** Mobile-only bottom navigation (BottomNavigation). Hidden at `md+`. */\n bottomNav?: ReactNode;\n /** Footer rendered after the main content. */\n footer?: ReactNode;\n /** Breakpoint at which the sidebar appears. Default `\"md\"`. */\n sidebarBreakpoint?: \"sm\" | \"md\" | \"lg\" | \"xl\";\n children?: ReactNode;\n}\n\n/**\n * Full app layout — composes [[Navbar]] + [[Sidebar]] + content +\n * [[BottomNavigation]] + footer with responsive behaviour:\n *\n * - **Desktop (`>= sidebarBreakpoint`)**: navbar + sidebar + main + footer.\n * - **Mobile (`< sidebarBreakpoint`)**: navbar + main + bottom nav + footer\n * (sidebar hidden — app should expose it via `<Drawer>` from a hamburger).\n *\n * @example\n * <AppShell\n * navbar={<Navbar logo={<Brand />} actions={<UserMenu />} />}\n * sidebar={<Sidebar items={...} value={tab} onChange={setTab} />}\n * bottomNav={<BottomNavigation items={...} value={tab} onChange={setTab} />}\n * footer={<Footer />}\n * >\n * <Page title=\"Dashboard\">{content}</Page>\n * </AppShell>\n */\nexport function AppShell({\n navbar,\n sidebar,\n bottomNav,\n footer,\n sidebarBreakpoint = \"md\",\n className,\n children,\n ...props\n}: AppShellProps) {\n const bp = useBreakpoint();\n const showSidebar = bp.above(sidebarBreakpoint);\n return (\n <div className={cn(styles.shell, className)} {...props}>\n {navbar && <div className={styles.navbar}>{navbar}</div>}\n <div className={styles.row}>\n {sidebar && showSidebar && <div className={styles.sidebar}>{sidebar}</div>}\n <main className={styles.main}>{children}</main>\n </div>\n {footer && <div className={styles.footer}>{footer}</div>}\n {bottomNav && !showSidebar && <div className={styles.bottomNav}>{bottomNav}</div>}\n </div>\n );\n}\n"],"mappings":";;;;;AAqCA,SAAgB,EAAS,EACrB,WACA,YACA,cACA,WACA,uBAAoB,MACpB,cACA,aACA,GAAG,KACW;CAEd,IAAM,IADK,EACS,CAAA,CAAG,MAAM,CAAiB;CAC9C,OACI,kBAAC,OAAD;EAAK,WAAW,EAAG,EAAO,OAAO,CAAS;EAAG,GAAI;
|
|
1
|
+
{"version":3,"file":"AppShell.js","names":[],"sources":["../../../src/components/AppShell/AppShell.tsx"],"sourcesContent":["import type { HTMLAttributes, ReactNode } from \"react\";\nimport { cn } from \"@/utils/cn\";\nimport { useBreakpoint } from \"@/hooks/use-breakpoint\";\nimport styles from \"./AppShell.module.css\";\n\nexport interface AppShellProps extends HTMLAttributes<HTMLDivElement> {\n /** Top navigation bar (Navbar / custom). Renders on every breakpoint. */\n navbar?: ReactNode;\n /** Side navigation (Sidebar). Hidden below `md` — pair with `<Drawer>` for mobile. */\n sidebar?: ReactNode;\n /** Mobile-only bottom navigation (BottomNavigation). Hidden at `md+`. */\n bottomNav?: ReactNode;\n /** Footer rendered after the main content. */\n footer?: ReactNode;\n /** Breakpoint at which the sidebar appears. Default `\"md\"`. */\n sidebarBreakpoint?: \"sm\" | \"md\" | \"lg\" | \"xl\";\n children?: ReactNode;\n}\n\n/**\n * Full app layout — composes [[Navbar]] + [[Sidebar]] + content +\n * [[BottomNavigation]] + footer with responsive behaviour:\n *\n * - **Desktop (`>= sidebarBreakpoint`)**: navbar + sidebar + main + footer.\n * - **Mobile (`< sidebarBreakpoint`)**: navbar + main + bottom nav + footer\n * (sidebar hidden — app should expose it via `<Drawer>` from a hamburger).\n *\n * @example\n * <AppShell\n * navbar={<Navbar logo={<Brand />} actions={<UserMenu />} />}\n * sidebar={<Sidebar items={...} value={tab} onChange={setTab} />}\n * bottomNav={<BottomNavigation items={...} value={tab} onChange={setTab} />}\n * footer={<Footer />}\n * >\n * <Page title=\"Dashboard\">{content}</Page>\n * </AppShell>\n */\nexport function AppShell({\n navbar,\n sidebar,\n bottomNav,\n footer,\n sidebarBreakpoint = \"md\",\n className,\n children,\n ...props\n}: AppShellProps) {\n const bp = useBreakpoint();\n const showSidebar = bp.above(sidebarBreakpoint);\n return (\n <div className={cn(styles.shell, className)} {...props}>\n {navbar && <div className={styles.navbar}>{navbar}</div>}\n <div className={styles.row}>\n {sidebar && showSidebar && <div className={styles.sidebar}>{sidebar}</div>}\n <main className={styles.main}>{children}</main>\n </div>\n {footer && <div className={styles.footer}>{footer}</div>}\n {bottomNav && !showSidebar && <div className={styles.bottomNav}>{bottomNav}</div>}\n </div>\n );\n}\n"],"mappings":";;;;;AAqCA,SAAgB,EAAS,EACrB,WACA,YACA,cACA,WACA,uBAAoB,MACpB,cACA,aACA,GAAG,KACW;CAEd,IAAM,IADK,EACS,CAAA,CAAG,MAAM,CAAiB;CAC9C,OACI,kBAAC,OAAD;EAAK,WAAW,EAAG,EAAO,OAAO,CAAS;EAAG,GAAI;EAAjD,UAAA;GACK,KAAU,kBAAC,OAAD;IAAK,WAAW,EAAO;IAAS,UAAA;GAAY,CAAA;GACvD,kBAAC,OAAD;IAAK,WAAW,EAAO;IAAvB,UAAA,CACK,KAAW,KAAe,kBAAC,OAAD;KAAK,WAAW,EAAO;KAAU,UAAA;IAAa,CAAA,GACzE,kBAAC,QAAD;KAAM,WAAW,EAAO;KAAO;IAAe,CAAA,CAC7C;;GACJ,KAAU,kBAAC,OAAD;IAAK,WAAW,EAAO;IAAS,UAAA;GAAY,CAAA;GACtD,KAAa,CAAC,KAAe,kBAAC,OAAD;IAAK,WAAW,EAAO;IAAY,UAAA;GAAe,CAAA;EAC/E;;AAEb"}
|