xertica-ui 2.2.1 → 2.4.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/CHANGELOG.md +564 -525
- package/README.md +417 -382
- package/bin/cli.ts +1244 -748
- package/bin/generate-tokens.ts +262 -262
- package/bin/language-config.ts +5 -8
- package/components/assets/xertica-orbe-animation.ts +1162 -1162
- package/components/assistant/code-block/CodeBlock.tsx +268 -268
- package/components/assistant/code-block/code-block.stories.tsx +57 -57
- package/components/assistant/code-block/code-block.test.tsx +44 -44
- package/components/assistant/code-block/index.ts +1 -1
- package/components/assistant/formatted-document/FormattedDocument.tsx +147 -147
- package/components/assistant/formatted-document/formatted-document.stories.tsx +51 -51
- package/components/assistant/formatted-document/formatted-document.test.tsx +42 -42
- package/components/assistant/formatted-document/index.ts +1 -1
- package/components/assistant/index.ts +6 -6
- package/components/assistant/markdown-message/MarkdownMessage.tsx +152 -152
- package/components/assistant/markdown-message/index.ts +1 -1
- package/components/assistant/markdown-message/markdown-message.stories.tsx +50 -50
- package/components/assistant/markdown-message/markdown-message.test.tsx +33 -33
- package/components/assistant/modern-chat-input/ModernChatInput.tsx +17 -7
- package/components/assistant/modern-chat-input/index.ts +1 -1
- package/components/assistant/modern-chat-input/modern-chat-input.stories.tsx +131 -131
- package/components/assistant/modern-chat-input/modern-chat-input.test.tsx +79 -79
- package/components/assistant/xertica-assistant/index.ts +3 -3
- package/components/assistant/xertica-assistant/parts/AssistantCollapsedView.tsx +99 -99
- package/components/assistant/xertica-assistant/parts/AssistantConversationList.tsx +104 -106
- package/components/assistant/xertica-assistant/parts/AssistantDocumentEditor.tsx +81 -81
- package/components/assistant/xertica-assistant/parts/AssistantFeedbackDialog.tsx +88 -78
- package/components/assistant/xertica-assistant/parts/AssistantHeader.tsx +75 -75
- package/components/assistant/xertica-assistant/parts/AssistantMessageBubble.tsx +564 -560
- package/components/assistant/xertica-assistant/parts/AssistantTabBar.tsx +67 -67
- package/components/assistant/xertica-assistant/parts/AssistantTypingIndicator.tsx +41 -41
- package/components/assistant/xertica-assistant/parts/AssistantWelcomeScreen.tsx +103 -103
- package/components/assistant/xertica-assistant/parts/index.ts +16 -16
- package/components/assistant/xertica-assistant/types.ts +134 -134
- package/components/assistant/xertica-assistant/use-assistant.ts +615 -615
- package/components/assistant/xertica-assistant/xertica-assistant.stories.tsx +407 -407
- package/components/assistant/xertica-assistant/xertica-assistant.test.tsx +65 -65
- package/components/assistant/xertica-assistant/xertica-assistant.tsx +611 -613
- package/components/blocks/card-patterns/ActivityCard.tsx +100 -100
- package/components/blocks/card-patterns/FeatureCard.tsx +109 -109
- package/components/blocks/card-patterns/FeatureCardSkeleton.tsx +1 -6
- package/components/blocks/card-patterns/NotificationCard.tsx +140 -140
- package/components/blocks/card-patterns/ProfileCard.tsx +112 -114
- package/components/blocks/card-patterns/ProjectCard.tsx +123 -123
- package/components/blocks/card-patterns/ProjectCardSkeleton.tsx +1 -6
- package/components/blocks/card-patterns/QuickActionCard.tsx +68 -68
- package/components/blocks/card-patterns/card-patterns.mdx +123 -123
- package/components/blocks/card-patterns/card-patterns.stories.tsx +594 -594
- package/components/blocks/card-patterns/index.ts +29 -29
- package/components/blocks/index.ts +1 -1
- package/components/brand/branding/branding.stories.tsx +57 -57
- package/components/brand/index.ts +6 -6
- package/components/brand/language-selector/index.ts +1 -1
- package/components/brand/language-selector/language-selector.mdx +126 -126
- package/components/brand/language-selector/language-selector.stories.tsx +1 -4
- package/components/brand/theme-toggle/ThemeToggle.tsx +74 -70
- package/components/brand/theme-toggle/index.ts +1 -1
- package/components/brand/theme-toggle/theme-toggle.stories.tsx +34 -34
- package/components/brand/theme-toggle/theme-toggle.test.tsx +34 -34
- package/components/brand/xertica-logo/XerticaLogo.stories.tsx +82 -82
- package/components/brand/xertica-logo/XerticaLogo.tsx +104 -104
- package/components/brand/xertica-logo/index.ts +1 -1
- package/components/brand/xertica-logo/xertica-logo.test.tsx +26 -26
- package/components/brand/xertica-orbe/XerticaOrbe.tsx +1927 -1927
- package/components/brand/xertica-orbe/index.ts +1 -1
- package/components/brand/xertica-orbe/xertica-orbe.stories.tsx +40 -40
- package/components/brand/xertica-orbe/xertica-orbe.test.tsx +19 -19
- package/components/brand/xertica-provider/XerticaProvider.tsx +1 -4
- package/components/brand/xertica-provider/index.ts +1 -1
- package/components/brand/xertica-provider/xertica-provider.test.tsx +74 -74
- package/components/brand/xertica-xlogo/XerticaXLogo.stories.tsx +79 -79
- package/components/brand/xertica-xlogo/XerticaXLogo.tsx +65 -65
- package/components/brand/xertica-xlogo/index.ts +1 -1
- package/components/brand/xertica-xlogo/xertica-xlogo.test.tsx +16 -16
- package/components/examples/ApiKeyMapExample.tsx +71 -71
- package/components/examples/DrawingMapExample.tsx +565 -565
- package/components/examples/FilterableMapExample.tsx +393 -393
- package/components/examples/LocationPickerExample.tsx +348 -348
- package/components/examples/MapExamples.tsx +268 -268
- package/components/examples/MapGmpExample.tsx +169 -169
- package/components/examples/MapShowcase.tsx +471 -471
- package/components/examples/RouteMapExamples.tsx +329 -329
- package/components/examples/SidebarLogoExample.tsx +65 -65
- package/components/examples/SimpleFilterableMap.tsx +219 -219
- package/components/examples/index.ts +45 -45
- package/components/figma/ImageWithFallback.tsx +27 -27
- package/components/hooks/index.ts +13 -13
- package/components/hooks/use-layout-shortcuts.ts +43 -43
- package/components/index.ts +86 -90
- package/components/layout/header/header.stories.tsx +204 -204
- package/components/layout/header/header.test.tsx +75 -75
- package/components/layout/header/header.tsx +349 -349
- package/components/layout/header/index.ts +1 -1
- package/components/layout/index.ts +2 -2
- package/components/layout/sidebar/index.ts +3 -3
- package/components/layout/sidebar/sidebar.stories.tsx +586 -586
- package/components/layout/sidebar/sidebar.test.tsx +76 -76
- package/components/layout/sidebar/sidebar.tsx +1079 -1073
- package/components/layout/sidebar/use-sidebar.ts +104 -104
- package/components/media/FloatingMediaWrapper.tsx +371 -371
- package/components/media/audio-player/AudioPlayer.stories.tsx +124 -124
- package/components/media/audio-player/AudioPlayer.test.tsx +106 -106
- package/components/media/audio-player/AudioPlayer.tsx +767 -765
- package/components/media/audio-player/index.ts +1 -1
- package/components/media/audio-player/use-audio-player.ts +312 -312
- package/components/media/index.ts +3 -3
- package/components/media/video-player/VideoPlayer.stories.tsx +98 -98
- package/components/media/video-player/VideoPlayer.test.tsx +73 -73
- package/components/media/video-player/VideoPlayer.tsx +310 -310
- package/components/media/video-player/index.ts +1 -1
- package/components/pages/forgot-password-page/ForgotPasswordPage.stories.tsx +24 -24
- package/components/pages/forgot-password-page/ForgotPasswordPage.tsx +188 -188
- package/components/pages/forgot-password-page/forgot-password-page.test.tsx +45 -45
- package/components/pages/forgot-password-page/index.ts +1 -1
- package/components/pages/home-content/HomeContent.stories.tsx +43 -43
- package/components/pages/home-content/HomeContent.tsx +120 -120
- package/components/pages/home-content/index.ts +1 -1
- package/components/pages/home-page/HomePage.stories.tsx +39 -39
- package/components/pages/home-page/HomePage.tsx +78 -74
- package/components/pages/home-page/home-page.test.tsx +53 -53
- package/components/pages/home-page/index.ts +1 -1
- package/components/pages/index.ts +8 -8
- package/components/pages/login-page/LoginPage.stories.tsx +39 -39
- package/components/pages/login-page/LoginPage.tsx +218 -216
- package/components/pages/login-page/index.ts +1 -1
- package/components/pages/login-page/login-page.test.tsx +63 -63
- package/components/pages/reset-password-page/ResetPasswordPage.stories.tsx +24 -24
- package/components/pages/reset-password-page/ResetPasswordPage.tsx +243 -239
- package/components/pages/reset-password-page/index.ts +1 -1
- package/components/pages/template-content/TemplateContent.stories.tsx +43 -43
- package/components/pages/template-content/TemplateContent.tsx +1354 -1235
- package/components/pages/template-content/index.ts +1 -1
- package/components/pages/template-page/TemplatePage.stories.tsx +39 -39
- package/components/pages/template-page/TemplatePage.tsx +62 -62
- package/components/pages/template-page/index.ts +1 -1
- package/components/pages/template-page/template-page.test.tsx +52 -52
- package/components/pages/verify-email-page/VerifyEmailPage.stories.tsx +41 -41
- package/components/pages/verify-email-page/VerifyEmailPage.tsx +206 -206
- package/components/pages/verify-email-page/index.ts +1 -1
- package/components/public-api-smoke.test.tsx +52 -52
- package/components/shared/CustomTooltipContent.tsx +48 -48
- package/components/shared/assistant-utils.test.ts +16 -16
- package/components/shared/assistant-utils.ts +225 -225
- package/components/shared/error-boundary.stories.tsx +114 -132
- package/components/shared/error-boundary.tsx +150 -154
- package/components/shared/error-fallbacks.tsx +222 -226
- package/components/shared/layout-constants.ts +8 -8
- package/components/shared/navigation.ts +35 -35
- package/components/shared/use-mobile.test.ts +16 -16
- package/components/shared/use-mobile.ts +36 -36
- package/components/shared/utils.test.ts +14 -14
- package/components/shared/utils.ts +6 -6
- package/components/ui/accordion/accordion.stories.tsx +105 -105
- package/components/ui/accordion/accordion.test.tsx +59 -59
- package/components/ui/accordion/accordion.tsx +77 -77
- package/components/ui/accordion/index.ts +1 -1
- package/components/ui/alert/alert.stories.tsx +86 -86
- package/components/ui/alert/alert.test.tsx +53 -53
- package/components/ui/alert/alert.tsx +93 -93
- package/components/ui/alert/index.ts +1 -1
- package/components/ui/alert-dialog/alert-dialog.stories.tsx +84 -84
- package/components/ui/alert-dialog/alert-dialog.test.tsx +70 -70
- package/components/ui/alert-dialog/alert-dialog.tsx +149 -149
- package/components/ui/alert-dialog/index.ts +1 -1
- package/components/ui/aspect-ratio/aspect-ratio.stories.tsx +46 -46
- package/components/ui/aspect-ratio/aspect-ratio.test.tsx +28 -28
- package/components/ui/aspect-ratio/aspect-ratio.tsx +20 -20
- package/components/ui/aspect-ratio/index.ts +1 -1
- package/components/ui/assistant-chart/AssistantChart.tsx +64 -64
- package/components/ui/assistant-chart/assistant-chart.stories.tsx +44 -44
- package/components/ui/assistant-chart/assistant-chart.test.tsx +46 -46
- package/components/ui/assistant-chart/index.ts +1 -1
- package/components/ui/avatar/avatar.stories.tsx +86 -86
- package/components/ui/avatar/avatar.test.tsx +55 -55
- package/components/ui/avatar/avatar.tsx +71 -71
- package/components/ui/avatar/index.ts +1 -1
- package/components/ui/badge/badge.stories.tsx +72 -72
- package/components/ui/badge/badge.test.tsx +40 -40
- package/components/ui/badge/badge.tsx +58 -58
- package/components/ui/badge/index.ts +1 -1
- package/components/ui/breadcrumb/breadcrumb.stories.tsx +123 -123
- package/components/ui/breadcrumb/breadcrumb.test.tsx +70 -70
- package/components/ui/breadcrumb/breadcrumb.tsx +114 -114
- package/components/ui/breadcrumb/index.ts +1 -1
- package/components/ui/button/button.stories.tsx +183 -183
- package/components/ui/button/button.test.tsx +64 -64
- package/components/ui/button/button.tsx +98 -98
- package/components/ui/button/index.ts +1 -1
- package/components/ui/calendar/calendar.stories.tsx +108 -108
- package/components/ui/calendar/calendar.test.tsx +53 -53
- package/components/ui/calendar/calendar.tsx +230 -230
- package/components/ui/calendar/index.ts +1 -1
- package/components/ui/card/card.stories.tsx +301 -301
- package/components/ui/card/card.test.tsx +55 -55
- package/components/ui/card/card.tsx +83 -83
- package/components/ui/card/index.ts +1 -1
- package/components/ui/carousel/carousel.stories.tsx +80 -80
- package/components/ui/carousel/carousel.test.tsx +75 -75
- package/components/ui/carousel/carousel.tsx +242 -242
- package/components/ui/carousel/index.ts +1 -1
- package/components/ui/chart/chart.stories.tsx +1328 -1328
- package/components/ui/chart/chart.test.tsx +178 -178
- package/components/ui/chart/chart.tsx +2232 -2232
- package/components/ui/chart/index.ts +1 -1
- package/components/ui/checkbox/checkbox.stories.tsx +109 -109
- package/components/ui/checkbox/checkbox.test.tsx +49 -49
- package/components/ui/checkbox/checkbox.tsx +68 -68
- package/components/ui/checkbox/index.ts +1 -1
- package/components/ui/collapsible/collapsible.stories.tsx +45 -45
- package/components/ui/collapsible/collapsible.test.tsx +51 -51
- package/components/ui/collapsible/collapsible.tsx +32 -32
- package/components/ui/collapsible/index.ts +1 -1
- package/components/ui/command/command.stories.tsx +134 -134
- package/components/ui/command/command.test.tsx +48 -48
- package/components/ui/command/command.tsx +163 -163
- package/components/ui/command/index.ts +1 -1
- package/components/ui/context-menu/context-menu.stories.tsx +76 -76
- package/components/ui/context-menu/context-menu.test.tsx +61 -61
- package/components/ui/context-menu/context-menu.tsx +236 -236
- package/components/ui/context-menu/index.ts +1 -1
- package/components/ui/dialog/dialog.stories.tsx +174 -174
- package/components/ui/dialog/dialog.test.tsx +78 -78
- package/components/ui/dialog/dialog.tsx +189 -189
- package/components/ui/dialog/index.ts +1 -1
- package/components/ui/drawer/drawer.stories.tsx +71 -71
- package/components/ui/drawer/drawer.test.tsx +67 -67
- package/components/ui/drawer/drawer.tsx +146 -146
- package/components/ui/drawer/index.ts +1 -1
- package/components/ui/dropdown-menu/dropdown-menu.stories.tsx +156 -156
- package/components/ui/dropdown-menu/dropdown-menu.test.tsx +62 -62
- package/components/ui/dropdown-menu/dropdown-menu.tsx +240 -240
- package/components/ui/dropdown-menu/index.ts +1 -1
- package/components/ui/empty/empty.stories.tsx +85 -85
- package/components/ui/empty/empty.test.tsx +31 -31
- package/components/ui/empty/empty.tsx +88 -88
- package/components/ui/empty/index.ts +1 -1
- package/components/ui/file-upload/file-upload.stories.tsx +144 -144
- package/components/ui/file-upload/file-upload.test.tsx +65 -65
- package/components/ui/file-upload/file-upload.tsx +142 -142
- package/components/ui/file-upload/index.ts +2 -2
- package/components/ui/file-upload/use-file-upload.ts +177 -177
- package/components/ui/form/form.stories.tsx +85 -85
- package/components/ui/form/form.test.tsx +75 -75
- package/components/ui/form/form.tsx +163 -163
- package/components/ui/form/index.ts +1 -1
- package/components/ui/google-maps-loader/google-maps-loader.test.tsx +35 -35
- package/components/ui/google-maps-loader/google-maps-loader.tsx +465 -465
- package/components/ui/google-maps-loader/index.ts +1 -1
- package/components/ui/hover-card/hover-card.stories.tsx +61 -61
- package/components/ui/hover-card/hover-card.test.tsx +48 -48
- package/components/ui/hover-card/hover-card.tsx +50 -50
- package/components/ui/hover-card/index.ts +1 -1
- package/components/ui/index.ts +400 -400
- package/components/ui/input/index.ts +1 -1
- package/components/ui/input/input.stories.tsx +153 -153
- package/components/ui/input/input.test.tsx +47 -47
- package/components/ui/input/input.tsx +57 -57
- package/components/ui/input-otp/index.ts +1 -1
- package/components/ui/input-otp/input-otp.stories.tsx +120 -120
- package/components/ui/input-otp/input-otp.test.tsx +74 -74
- package/components/ui/input-otp/input-otp.tsx +101 -101
- package/components/ui/label/index.ts +1 -1
- package/components/ui/label/label.stories.tsx +74 -74
- package/components/ui/label/label.test.tsx +45 -45
- package/components/ui/label/label.tsx +53 -53
- package/components/ui/map/index.ts +1 -1
- package/components/ui/map/map.stories.tsx +86 -86
- package/components/ui/map/map.test.tsx +82 -82
- package/components/ui/map/map.tsx +506 -506
- package/components/ui/map/mock.test.tsx +13 -13
- package/components/ui/map-config/index.ts +1 -1
- package/components/ui/map-config/map-config.ts +18 -18
- package/components/ui/map-layers/index.ts +1 -1
- package/components/ui/map-layers/map-layers.test.tsx +48 -48
- package/components/ui/map-layers/map-layers.tsx +126 -126
- package/components/ui/map.exports/index.ts +1 -1
- package/components/ui/map.exports/map.exports.ts +31 -31
- package/components/ui/menubar/index.ts +1 -1
- package/components/ui/menubar/menubar.stories.tsx +130 -130
- package/components/ui/menubar/menubar.test.tsx +53 -53
- package/components/ui/menubar/menubar.tsx +265 -265
- package/components/ui/navigation-menu/index.ts +1 -1
- package/components/ui/navigation-menu/navigation-menu.stories.tsx +126 -126
- package/components/ui/navigation-menu/navigation-menu.test.tsx +47 -47
- package/components/ui/navigation-menu/navigation-menu.tsx +165 -165
- package/components/ui/notification-badge/index.ts +1 -1
- package/components/ui/notification-badge/notification-badge.stories.tsx +66 -66
- package/components/ui/notification-badge/notification-badge.test.tsx +61 -61
- package/components/ui/notification-badge/notification-badge.tsx +91 -91
- package/components/ui/page-header/index.ts +1 -1
- package/components/ui/page-header/page-header.stories.tsx +69 -69
- package/components/ui/page-header/page-header.test.tsx +37 -37
- package/components/ui/page-header/page-header.tsx +124 -124
- package/components/ui/pagination/index.ts +3 -3
- package/components/ui/pagination/pagination.stories.tsx +210 -210
- package/components/ui/pagination/pagination.test.tsx +63 -63
- package/components/ui/pagination/pagination.tsx +140 -140
- package/components/ui/pagination/use-pagination.ts +173 -173
- package/components/ui/popover/index.ts +1 -1
- package/components/ui/popover/popover.stories.tsx +73 -73
- package/components/ui/popover/popover.test.tsx +48 -48
- package/components/ui/popover/popover.tsx +54 -54
- package/components/ui/progress/index.ts +1 -1
- package/components/ui/progress/progress.stories.tsx +55 -55
- package/components/ui/progress/progress.test.tsx +23 -23
- package/components/ui/progress/progress.tsx +68 -68
- package/components/ui/radio-group/index.ts +1 -1
- package/components/ui/radio-group/radio-group.stories.tsx +114 -114
- package/components/ui/radio-group/radio-group.test.tsx +78 -78
- package/components/ui/radio-group/radio-group.tsx +93 -93
- package/components/ui/rating/index.ts +1 -1
- package/components/ui/rating/rating.stories.tsx +50 -50
- package/components/ui/rating/rating.test.tsx +48 -48
- package/components/ui/rating/rating.tsx +145 -145
- package/components/ui/resizable/index.ts +1 -1
- package/components/ui/resizable/resizable.stories.tsx +88 -88
- package/components/ui/resizable/resizable.test.tsx +61 -61
- package/components/ui/resizable/resizable.tsx +452 -452
- package/components/ui/rich-text-editor/index.ts +7 -7
- package/components/ui/rich-text-editor/rich-text-editor.stories.tsx +290 -290
- package/components/ui/rich-text-editor/rich-text-editor.test.tsx +86 -86
- package/components/ui/rich-text-editor/rich-text-editor.tsx +634 -634
- package/components/ui/rich-text-editor/use-rich-text-editor.ts +453 -453
- package/components/ui/route-map/index.ts +1 -1
- package/components/ui/route-map/route-map.stories.tsx +48 -48
- package/components/ui/route-map/route-map.test.tsx +108 -108
- package/components/ui/route-map/route-map.tsx +349 -349
- package/components/ui/scroll-area/index.ts +1 -1
- package/components/ui/scroll-area/scroll-area.stories.tsx +31 -31
- package/components/ui/scroll-area/scroll-area.test.tsx +27 -27
- package/components/ui/scroll-area/scroll-area.tsx +70 -70
- package/components/ui/search/index.ts +1 -1
- package/components/ui/search/search.stories.tsx +107 -107
- package/components/ui/search/search.test.tsx +67 -67
- package/components/ui/search/search.tsx +141 -141
- package/components/ui/select/index.ts +1 -1
- package/components/ui/select/select.stories.tsx +163 -163
- package/components/ui/select/select.test.tsx +99 -99
- package/components/ui/select/select.tsx +195 -195
- package/components/ui/separator/index.ts +1 -1
- package/components/ui/separator/separator.stories.tsx +55 -55
- package/components/ui/separator/separator.test.tsx +23 -23
- package/components/ui/separator/separator.tsx +39 -39
- package/components/ui/sheet/index.ts +1 -1
- package/components/ui/sheet/sheet.stories.tsx +93 -93
- package/components/ui/sheet/sheet.test.tsx +62 -62
- package/components/ui/sheet/sheet.tsx +149 -149
- package/components/ui/simple-map/index.ts +1 -1
- package/components/ui/simple-map/simple-map.stories.tsx +44 -44
- package/components/ui/simple-map/simple-map.test.tsx +36 -36
- package/components/ui/simple-map/simple-map.tsx +92 -92
- package/components/ui/skeleton/index.ts +1 -1
- package/components/ui/skeleton/skeleton.stories.tsx +36 -36
- package/components/ui/skeleton/skeleton.test.tsx +19 -19
- package/components/ui/skeleton/skeleton.tsx +25 -25
- package/components/ui/slider/index.ts +1 -1
- package/components/ui/slider/slider.stories.tsx +44 -44
- package/components/ui/slider/slider.test.tsx +25 -25
- package/components/ui/slider/slider.tsx +66 -66
- package/components/ui/sonner/index.ts +1 -1
- package/components/ui/sonner/sonner.stories.tsx +41 -41
- package/components/ui/sonner/sonner.test.tsx +24 -24
- package/components/ui/sonner/sonner.tsx +74 -74
- package/components/ui/stats-card/index.ts +2 -2
- package/components/ui/stats-card/stats-card-skeleton.tsx +1 -3
- package/components/ui/stats-card/stats-card.stories.tsx +99 -99
- package/components/ui/stats-card/stats-card.test.tsx +34 -34
- package/components/ui/stats-card/stats-card.tsx +93 -93
- package/components/ui/stepper/index.ts +3 -3
- package/components/ui/stepper/stepper.stories.tsx +171 -171
- package/components/ui/stepper/stepper.test.tsx +47 -47
- package/components/ui/stepper/stepper.tsx +190 -190
- package/components/ui/stepper/use-stepper.ts +139 -139
- package/components/ui/switch/index.ts +1 -1
- package/components/ui/switch/switch.stories.tsx +93 -93
- package/components/ui/switch/switch.test.tsx +44 -44
- package/components/ui/switch/switch.tsx +70 -70
- package/components/ui/table/index.ts +1 -1
- package/components/ui/table/table.stories.tsx +114 -114
- package/components/ui/table/table.test.tsx +43 -43
- package/components/ui/table/table.tsx +104 -104
- package/components/ui/tabs/index.ts +1 -1
- package/components/ui/tabs/tabs.stories.tsx +140 -140
- package/components/ui/tabs/tabs.test.tsx +50 -50
- package/components/ui/tabs/tabs.tsx +66 -66
- package/components/ui/textarea/index.ts +1 -1
- package/components/ui/textarea/textarea.stories.tsx +69 -69
- package/components/ui/textarea/textarea.test.tsx +41 -41
- package/components/ui/textarea/textarea.tsx +61 -61
- package/components/ui/timeline/index.ts +1 -1
- package/components/ui/timeline/timeline.stories.tsx +97 -97
- package/components/ui/timeline/timeline.test.tsx +53 -53
- package/components/ui/timeline/timeline.tsx +124 -124
- package/components/ui/toggle/index.ts +1 -1
- package/components/ui/toggle/toggle.stories.tsx +56 -56
- package/components/ui/toggle/toggle.test.tsx +32 -32
- package/components/ui/toggle/toggle.tsx +55 -55
- package/components/ui/toggle-group/index.ts +1 -1
- package/components/ui/toggle-group/toggle-group.stories.tsx +66 -66
- package/components/ui/toggle-group/toggle-group.test.tsx +47 -47
- package/components/ui/toggle-group/toggle-group.tsx +79 -79
- package/components/ui/tooltip/index.ts +1 -1
- package/components/ui/tooltip/tooltip.stories.tsx +83 -83
- package/components/ui/tooltip/tooltip.test.tsx +39 -39
- package/components/ui/tooltip/tooltip.tsx +69 -69
- package/components/ui/tree-view/index.ts +4 -4
- package/components/ui/tree-view/tree-view.stories.tsx +154 -154
- package/components/ui/tree-view/tree-view.test.tsx +58 -58
- package/components/ui/tree-view/tree-view.tsx +171 -171
- package/components/ui/tree-view/use-tree-view.ts +237 -237
- package/components.json +892 -892
- package/contexts/ApiKeyContext.test.tsx +26 -26
- package/contexts/ApiKeyContext.tsx +196 -196
- package/contexts/AssistenteContext.test.tsx +17 -17
- package/contexts/AssistenteContext.tsx +113 -113
- package/contexts/AuthContext.tsx +121 -118
- package/contexts/BrandColorsContext.test.tsx +21 -21
- package/contexts/BrandColorsContext.tsx +251 -251
- package/contexts/LanguageContext.tsx +1 -2
- package/contexts/LayoutContext.test.tsx +29 -29
- package/contexts/LayoutContext.tsx +140 -140
- package/contexts/ThemeContext.test.tsx +38 -38
- package/contexts/ThemeContext.tsx +111 -111
- package/contexts/index.ts +8 -8
- package/contexts/theme-data.ts +340 -340
- package/dist/AssistantChart-COGiOV-g.cjs +3541 -0
- package/dist/AssistantChart-CWX1OWNM.js +3373 -0
- package/dist/AudioPlayer-9psiEucT.cjs +1282 -0
- package/dist/AudioPlayer-Dp2bD1Gk.js +1278 -0
- package/dist/BrandColorsContext-DZT7JjeD.js +659 -0
- package/dist/BrandColorsContext-awnBCmC4.cjs +666 -0
- package/dist/CodeBlock-DYkTfR0f.js +221 -0
- package/dist/CodeBlock-EOvp9cVu.cjs +223 -0
- package/dist/CustomTooltipContent-BhdIeBEg.cjs +54 -0
- package/dist/CustomTooltipContent-CNbVB2NS.js +33 -0
- package/dist/FeatureCard-BZ4CYxFf.cjs +497 -0
- package/dist/FeatureCard-DNycVGwT.js +485 -0
- package/dist/FeatureCardSkeleton-DZqc96mt.js +27 -0
- package/dist/FeatureCardSkeleton-pTa0YNKP.cjs +29 -0
- package/dist/LayoutContext-BEq_-n98.cjs +96 -0
- package/dist/LayoutContext-DNl1xSoX.js +92 -0
- package/dist/ThemeContext-CMD3z2Dz.cjs +1930 -0
- package/dist/ThemeContext-x_F2zsnv.js +1923 -0
- package/dist/VerifyEmailPage-BJjAMUTW.js +3223 -0
- package/dist/VerifyEmailPage-Bv8Ah_TK.cjs +3235 -0
- package/dist/VerifyEmailPage-CkBYfsNy.cjs +3232 -0
- package/dist/VerifyEmailPage-Cyl55sJb.js +3226 -0
- package/dist/VerifyEmailPage-X14vhdyl.js +3296 -0
- package/dist/VerifyEmailPage-u_Dn7t1U.cjs +3305 -0
- package/dist/XerticaOrbe-Uk2JML1-.cjs +1927 -0
- package/dist/XerticaOrbe-jA5T2iOk.js +1925 -0
- package/dist/XerticaProvider-BErr83Bg.js +42 -0
- package/dist/XerticaProvider-CwOkHxiT.cjs +44 -0
- package/dist/XerticaProvider-DUOJg9iX.js +49 -0
- package/dist/XerticaProvider-Dl_b72_l.cjs +51 -0
- package/dist/XerticaXLogo-BX3ueACh.js +255 -0
- package/dist/XerticaXLogo-mqjoBiLI.js +252 -0
- package/dist/XerticaXLogo-qBPhwK3g.cjs +260 -0
- package/dist/XerticaXLogo-uQgwns_E.cjs +257 -0
- package/dist/alert-dialog-DhwPioBa.cjs +885 -0
- package/dist/alert-dialog-DqlRW_An.js +831 -0
- package/dist/assistant.cjs.js +8 -4
- package/dist/assistant.es.js +5 -11
- package/dist/avatar-3kO2Anrp.js +54 -0
- package/dist/avatar-BCM7YQRC.cjs +77 -0
- package/dist/blocks.cjs.js +9 -4
- package/dist/blocks.es.js +2 -16
- package/dist/brand.cjs.js +10 -5
- package/dist/brand.es.js +3 -11
- package/dist/breadcrumb-BKtHF4gk.cjs +98 -0
- package/dist/breadcrumb-ifNsA7Zl.js +90 -0
- package/dist/button-0BlA47It.cjs +85 -0
- package/dist/button-DZHzN1Gd.js +62 -0
- package/dist/cli.js +471 -93
- package/dist/components/brand/theme-toggle/ThemeToggle.d.ts +1 -1
- package/dist/components/index.d.ts +1 -1
- package/dist/dropdown-menu-BMcykFDf.cjs +225 -0
- package/dist/dropdown-menu-Dn_eV2Xb.js +190 -0
- package/dist/google-maps-loader-BCe58h9D.js +308 -0
- package/dist/google-maps-loader-casMyxlo.cjs +316 -0
- package/dist/hooks.cjs.js +12 -8
- package/dist/hooks.es.js +10 -27
- package/dist/index-9GWd0qxq.cjs +12 -0
- package/dist/index-BabBx2pa.js +6 -0
- package/dist/index.cjs.js +37 -32
- package/dist/index.es.js +30 -363
- package/dist/input-C_UiS2Py.cjs +152 -0
- package/dist/input-cc-PTD4R.js +123 -0
- package/dist/layout.cjs.js +10 -6
- package/dist/layout.es.js +7 -9
- package/dist/media.cjs.js +8 -3
- package/dist/media.es.js +1 -6
- package/dist/pages.cjs.js +8 -3
- package/dist/pages.es.js +1 -11
- package/dist/progress-C7Lti5wo.js +80 -0
- package/dist/progress-Cqwxbqs1.cjs +103 -0
- package/dist/rich-text-editor-DqLICivI.js +2832 -0
- package/dist/rich-text-editor-DxO1Hz3a.cjs +2903 -0
- package/dist/select-CH6v_KcQ.cjs +161 -0
- package/dist/select-D-xvCZK2.js +130 -0
- package/dist/sidebar-3XyzjVBw.js +792 -0
- package/dist/sidebar-B4ZWaMrE.js +792 -0
- package/dist/sidebar-BS1p2V7t.cjs +795 -0
- package/dist/sidebar-DyYvgyBj.cjs +795 -0
- package/dist/skeleton-DjiHerJn.cjs +87 -0
- package/dist/skeleton-DtR5tkYe.js +78 -0
- package/dist/slider-B00b9SVK.cjs +78 -0
- package/dist/slider-DQCNUUMj.js +56 -0
- package/dist/sonner-B-jWlik1.cjs +68 -0
- package/dist/sonner-C9tiqj4f.js +47 -0
- package/dist/tooltip-D8n9UYoU.cjs +72 -0
- package/dist/tooltip-RtbSmPYJ.js +48 -0
- package/dist/ui.cjs.js +23 -18
- package/dist/ui.es.js +16 -303
- package/dist/use-audio-player-B78fd2ct.js +188 -0
- package/dist/use-audio-player-DGvhPrgR.cjs +190 -0
- package/dist/use-mobile-BdXTRb0Z.cjs +51 -0
- package/dist/use-mobile-Ce2cBAQe.js +29 -0
- package/dist/xertica-assistant-B1NaSFFj.js +2173 -0
- package/dist/xertica-assistant-B687qEPU.js +2165 -0
- package/dist/xertica-assistant-CIaUlbIt.cjs +2180 -0
- package/dist/xertica-assistant-sOHwTgIP.cjs +2172 -0
- package/dist/xertica-ui.css +1 -1
- package/docs/ai-usage.md +195 -195
- package/docs/architecture-improvements.md +456 -456
- package/docs/architecture.md +312 -306
- package/docs/components/accordion.md +109 -109
- package/docs/components/alert-dialog.md +127 -127
- package/docs/components/alert.md +106 -106
- package/docs/components/aspect-ratio.md +58 -58
- package/docs/components/assistant-chart.md +47 -47
- package/docs/components/assistant.md +428 -426
- package/docs/components/audio-player.md +167 -167
- package/docs/components/avatar.md +101 -101
- package/docs/components/badge.md +84 -84
- package/docs/components/branding.md +252 -252
- package/docs/components/breadcrumb.md +104 -104
- package/docs/components/button.md +156 -156
- package/docs/components/calendar.md +141 -141
- package/docs/components/card-patterns.md +447 -445
- package/docs/components/card.md +245 -245
- package/docs/components/carousel.md +100 -100
- package/docs/components/chart.md +638 -638
- package/docs/components/checkbox.md +88 -88
- package/docs/components/code-block.md +105 -105
- package/docs/components/collapsible.md +86 -86
- package/docs/components/command.md +113 -113
- package/docs/components/context-menu.md +81 -81
- package/docs/components/dialog.md +198 -198
- package/docs/components/drawer.md +105 -105
- package/docs/components/dropdown-menu.md +127 -127
- package/docs/components/empty.md +127 -127
- package/docs/components/error-boundary.md +201 -191
- package/docs/components/file-upload.md +189 -189
- package/docs/components/floating-media-wrapper.md +63 -63
- package/docs/components/form.md +177 -177
- package/docs/components/formatted-document.md +105 -105
- package/docs/components/google-maps-loader.md +44 -44
- package/docs/components/header.md +177 -177
- package/docs/components/hooks.md +432 -430
- package/docs/components/hover-card.md +86 -86
- package/docs/components/image-with-fallback.md +107 -107
- package/docs/components/input-otp.md +95 -95
- package/docs/components/input.md +130 -130
- package/docs/components/label.md +69 -69
- package/docs/components/language-selector.md +20 -16
- package/docs/components/map-layers.md +138 -138
- package/docs/components/map.md +84 -84
- package/docs/components/markdown-message.md +47 -47
- package/docs/components/menubar.md +89 -89
- package/docs/components/modern-chat-input.md +164 -164
- package/docs/components/navigation-menu.md +83 -83
- package/docs/components/notification-badge.md +78 -78
- package/docs/components/page-header.md +93 -93
- package/docs/components/pages.md +323 -309
- package/docs/components/pagination.md +334 -334
- package/docs/components/popover.md +116 -116
- package/docs/components/progress.md +103 -103
- package/docs/components/radio-group.md +133 -133
- package/docs/components/rating.md +77 -77
- package/docs/components/resizable.md +84 -84
- package/docs/components/rich-text-editor.md +255 -255
- package/docs/components/route-map.md +124 -124
- package/docs/components/scroll-area.md +58 -58
- package/docs/components/search.md +87 -87
- package/docs/components/select.md +144 -144
- package/docs/components/separator.md +58 -58
- package/docs/components/sheet.md +122 -122
- package/docs/components/sidebar.md +314 -314
- package/docs/components/simple-map.md +51 -51
- package/docs/components/skeleton.md +99 -99
- package/docs/components/slider.md +84 -84
- package/docs/components/sonner.md +115 -115
- package/docs/components/stats-card.md +120 -120
- package/docs/components/stepper.md +268 -268
- package/docs/components/switch.md +106 -106
- package/docs/components/table.md +138 -138
- package/docs/components/tabs.md +117 -117
- package/docs/components/textarea.md +86 -86
- package/docs/components/theme-toggle.md +73 -73
- package/docs/components/timeline.md +121 -121
- package/docs/components/toggle-group.md +68 -68
- package/docs/components/toggle.md +62 -62
- package/docs/components/tooltip.md +116 -116
- package/docs/components/tree-view.md +238 -238
- package/docs/components/use-mobile.md +96 -96
- package/docs/components/video-player.md +68 -68
- package/docs/components/xertica-logo.md +36 -36
- package/docs/components/xertica-orbe.md +35 -35
- package/docs/components/xertica-provider.md +65 -65
- package/docs/components/xertica-xlogo.md +35 -35
- package/docs/decision-tree.md +293 -293
- package/docs/doc-audit.md +244 -243
- package/docs/form-sizing.md +162 -162
- package/docs/getting-started.md +616 -591
- package/docs/guidelines.md +330 -328
- package/docs/i18n.md +61 -57
- package/docs/installation.md +268 -267
- package/docs/layout.md +143 -143
- package/docs/llms.md +295 -295
- package/docs/patterns/analytics.md +194 -194
- package/docs/patterns/crud.md +149 -149
- package/docs/patterns/dashboard.md +138 -138
- package/docs/patterns/detail-page.md +296 -296
- package/docs/patterns/form.md +241 -241
- package/docs/patterns/login.md +156 -156
- package/docs/patterns/settings.md +368 -368
- package/docs/patterns/wizard.md +213 -213
- package/docs/state-management.md +289 -289
- package/guidelines/Guidelines.md +409 -406
- package/hooks/useTheme.test.tsx +16 -16
- package/hooks/useTheme.ts +4 -4
- package/imports/Podcast.tsx +540 -540
- package/imports/XerticaAi.tsx +46 -46
- package/imports/XerticaX.tsx +15 -15
- package/imports/svg-aueiaqngck.ts +20 -20
- package/imports/svg-v9krss1ozd.ts +23 -23
- package/imports/svg-vhrdofe3qe.ts +6 -6
- package/llms-compact.txt +2 -1
- package/llms.txt +2 -1
- package/mcp/resources.json +22 -22
- package/mcp/tools.json +35 -35
- package/package.json +219 -213
- package/scripts/ai-validator.ts +91 -91
- package/scripts/cleanup-case-dupes.ts +62 -62
- package/scripts/generate-ai-manifests.ts +107 -107
- package/styles/globals.css +13 -13
- package/styles/xertica/app-overrides/chat.css +61 -61
- package/styles/xertica/app-overrides/scrollbar.css +33 -33
- package/styles/xertica/base.css +90 -71
- package/styles/xertica/integrations/google-maps.css +76 -76
- package/styles/xertica/integrations/sonner.css +73 -73
- package/styles/xertica/theme-map.css +102 -99
- package/styles/xertica/tokens.css +240 -236
- package/templates/CLAUDE.md +16 -1
- package/templates/eslint.config.js +26 -26
- package/templates/guidelines/Guidelines.md +577 -553
- package/templates/package.json +69 -69
- package/templates/postcss.config.js +6 -6
- package/templates/src/app/App.tsx +46 -46
- package/templates/src/app/components/AppLayout.tsx +55 -55
- package/templates/src/app/components/AuthGuard.tsx +131 -82
- package/templates/src/app/context/AuthContext.tsx +108 -108
- package/templates/src/features/assistant/index.ts +5 -5
- package/templates/src/features/auth/index.ts +4 -4
- package/templates/src/features/auth/ui/AuthPageShell.tsx +32 -32
- package/templates/src/features/auth/ui/ForgotPasswordContent.tsx +70 -72
- package/templates/src/features/auth/ui/LoginContent.tsx +92 -92
- package/templates/src/features/auth/ui/ResetPasswordContent.tsx +6 -2
- package/templates/src/features/auth/ui/SocialLoginButtons.tsx +78 -78
- package/templates/src/features/auth/ui/VerifyEmailContent.tsx +2 -6
- package/templates/src/features/home/data/mock.ts +41 -35
- package/templates/src/features/home/index.ts +11 -11
- package/templates/src/features/home/store/dashboardStore.ts +25 -25
- package/templates/src/features/home/ui/HomeContent.tsx +117 -119
- package/templates/src/features/template/index.ts +5 -5
- package/templates/src/features/template/ui/CrudTemplate.tsx +1 -4
- package/templates/src/features/template/ui/LoginTemplate.tsx +1 -1
- package/templates/src/features/template/ui/TemplateContent.tsx +29 -21
- package/templates/src/locales/en/pages/templates.json +17 -17
- package/templates/src/locales/es/pages/templates.json +17 -17
- package/templates/src/locales/pt-BR/pages/templates.json +17 -17
- package/templates/src/main.tsx +11 -11
- package/templates/src/pages/AssistantPage.tsx +26 -20
- package/templates/src/pages/ForgotPasswordPage.tsx +6 -6
- package/templates/src/pages/HomePage.tsx +53 -49
- package/templates/src/pages/LoginPage.tsx +10 -10
- package/templates/src/pages/ResetPasswordPage.tsx +6 -6
- package/templates/src/pages/TemplatePage.tsx +28 -28
- package/templates/src/pages/VerifyEmailPage.tsx +6 -6
- package/templates/src/shared/config/navigation.ts +19 -19
- package/templates/src/shared/error-boundary.tsx +150 -154
- package/templates/src/shared/error-fallbacks.tsx +222 -226
- package/templates/src/shared/lib/auth.ts +20 -20
- package/templates/src/shared/types/auth.ts +3 -3
- package/templates/src/styles/index.css +95 -95
- package/templates/src/styles/xertica/tokens.css +240 -236
- package/templates/tsconfig.json +25 -25
- package/templates/tsconfig.node.json +12 -12
- package/templates/vite-env.d.ts +1 -1
- package/templates/vite.config.js +20 -20
- package/templates/vite.config.ts +54 -51
- package/utils/color-utils.ts +72 -72
- package/utils/demo-responses.test.ts +10 -10
- package/utils/demo-responses.ts +151 -151
- package/utils/gemini.test.ts +25 -25
- package/utils/gemini.ts +155 -155
package/docs/state-management.md
CHANGED
|
@@ -1,289 +1,289 @@
|
|
|
1
|
-
# State Management — Xertica UI
|
|
2
|
-
|
|
3
|
-
Projects scaffolded with `xertica-ui init` use a layered state strategy:
|
|
4
|
-
|
|
5
|
-
| Layer
|
|
6
|
-
|
|
7
|
-
| **Server state**
|
|
8
|
-
| **Client UI state**
|
|
9
|
-
| **Auth state**
|
|
10
|
-
| **Layout state**
|
|
11
|
-
| **Local component state** | `useState`
|
|
12
|
-
|
|
13
|
-
---
|
|
14
|
-
|
|
15
|
-
## Features Slice Structure
|
|
16
|
-
|
|
17
|
-
All server data and client UI state lives in `src/features/<domain>/`. The scaffold template (`npx xertica-ui init`) generates the following structure:
|
|
18
|
-
|
|
19
|
-
```
|
|
20
|
-
src/features/
|
|
21
|
-
├── home/
|
|
22
|
-
│ ├── data/
|
|
23
|
-
│ │ └── mock.ts ← types + mock data + async fetch functions
|
|
24
|
-
│ ├── hooks/
|
|
25
|
-
│ │ └── useFeatureCards.ts ← React Query hook (language-aware)
|
|
26
|
-
│ ├── store/
|
|
27
|
-
│ │ └── dashboardStore.ts ← Zustand store
|
|
28
|
-
│ ├── ui/
|
|
29
|
-
│ │ └── HomeContent.tsx ← page content component
|
|
30
|
-
│ └── index.ts ← barrel (re-exports everything)
|
|
31
|
-
└── assistant/
|
|
32
|
-
├── data/
|
|
33
|
-
│ └── mock.ts ← AssistantConfig + fetchAssistantConfig + factory functions
|
|
34
|
-
├── hooks/
|
|
35
|
-
│ └── useAssistantConfig.ts ← React Query hook (language-aware)
|
|
36
|
-
└── index.ts
|
|
37
|
-
```
|
|
38
|
-
|
|
39
|
-
> The library source (`xertica-ui` package) contains additional hooks (`useDashboardStats`, `useTeamMembers`) for the design-system showcase pages. These are not generated in the scaffold template.
|
|
40
|
-
|
|
41
|
-
### Adding a New Feature
|
|
42
|
-
|
|
43
|
-
1. Create `features/<name>/data/mock.ts` with your types and mock fetch functions
|
|
44
|
-
2. Create `features/<name>/hooks/use<Name>.ts` wrapping the fetch function in `useQuery`
|
|
45
|
-
3. Create `features/<name>/store/<name>Store.ts` for any client-side UI state
|
|
46
|
-
4. Re-export from `features/<name>/index.ts`
|
|
47
|
-
5. Consume in your component via the hook
|
|
48
|
-
|
|
49
|
-
---
|
|
50
|
-
|
|
51
|
-
## React Query (Server State)
|
|
52
|
-
|
|
53
|
-
### Setup
|
|
54
|
-
|
|
55
|
-
`QueryClientProvider` is placed at the root of the provider stack, outside `XerticaProvider`:
|
|
56
|
-
|
|
57
|
-
```tsx
|
|
58
|
-
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
|
59
|
-
|
|
60
|
-
const queryClient = new QueryClient({
|
|
61
|
-
defaultOptions: {
|
|
62
|
-
queries: {
|
|
63
|
-
retry: 1,
|
|
64
|
-
refetchOnWindowFocus: false,
|
|
65
|
-
},
|
|
66
|
-
},
|
|
67
|
-
});
|
|
68
|
-
|
|
69
|
-
function App() {
|
|
70
|
-
return (
|
|
71
|
-
<QueryClientProvider client={queryClient}>
|
|
72
|
-
<XerticaProvider>
|
|
73
|
-
<Router>...</Router>
|
|
74
|
-
</XerticaProvider>
|
|
75
|
-
</QueryClientProvider>
|
|
76
|
-
);
|
|
77
|
-
}
|
|
78
|
-
```
|
|
79
|
-
|
|
80
|
-
### Writing a Hook
|
|
81
|
-
|
|
82
|
-
Hooks that return translated strings must include the active **language** in their `queryKey`. This gives each locale its own cache slot — switching language creates a new cache key, triggers a refetch in the new language, and switching back is an instant cache hit.
|
|
83
|
-
|
|
84
|
-
```ts
|
|
85
|
-
// features/home/hooks/useTeamMembers.ts
|
|
86
|
-
import { useQuery } from '@tanstack/react-query';
|
|
87
|
-
import { useLanguage } from 'xertica-ui/hooks';
|
|
88
|
-
import { fetchTeamMembers, type TeamMember } from '../data/mock';
|
|
89
|
-
|
|
90
|
-
export function useTeamMembers() {
|
|
91
|
-
const { language } = useLanguage(); // ← current locale from LanguageContext
|
|
92
|
-
return useQuery<TeamMember[]>({
|
|
93
|
-
queryKey: ['home', 'team-members', language], // ← language as third element
|
|
94
|
-
queryFn: fetchTeamMembers,
|
|
95
|
-
staleTime: 2 * 60 * 1000, // 2 min
|
|
96
|
-
});
|
|
97
|
-
}
|
|
98
|
-
```
|
|
99
|
-
|
|
100
|
-
> **Note:** Do not export a named `*_KEY` constant. The key now includes `language`, which is only available at hook call-time, making a module-level constant impossible. Keep the key inlined in the hook.
|
|
101
|
-
|
|
102
|
-
### Consuming in a Component
|
|
103
|
-
|
|
104
|
-
```tsx
|
|
105
|
-
import { useTeamMembers } from '../../../features/home';
|
|
106
|
-
|
|
107
|
-
export function TeamTable() {
|
|
108
|
-
const { data: members = [], isLoading, isError } = useTeamMembers();
|
|
109
|
-
|
|
110
|
-
if (isLoading) return <Skeleton />;
|
|
111
|
-
if (isError) return <ErrorState />;
|
|
112
|
-
|
|
113
|
-
return (
|
|
114
|
-
<Table>
|
|
115
|
-
<TableBody>
|
|
116
|
-
{members.map(m => (
|
|
117
|
-
<TableRow key={m.id}>
|
|
118
|
-
<TableCell>{m.name}</TableCell>
|
|
119
|
-
<TableCell>{m.email}</TableCell>
|
|
120
|
-
</TableRow>
|
|
121
|
-
))}
|
|
122
|
-
</TableBody>
|
|
123
|
-
</Table>
|
|
124
|
-
);
|
|
125
|
-
}
|
|
126
|
-
```
|
|
127
|
-
|
|
128
|
-
### Connecting to a Real API
|
|
129
|
-
|
|
130
|
-
Replace only the `fetch*` function in `data/mock.ts` — the hook and component stay unchanged:
|
|
131
|
-
|
|
132
|
-
```ts
|
|
133
|
-
// features/home/data/mock.ts
|
|
134
|
-
|
|
135
|
-
// Before (mock)
|
|
136
|
-
export async function fetchTeamMembers(): Promise<TeamMember[]> {
|
|
137
|
-
await new Promise(resolve => setTimeout(resolve, 250));
|
|
138
|
-
return MOCK_TEAM_MEMBERS;
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
// After (real API)
|
|
142
|
-
export async function fetchTeamMembers(): Promise<TeamMember[]> {
|
|
143
|
-
const res = await fetch('/api/team/members');
|
|
144
|
-
if (!res.ok) throw new Error('Failed to fetch team members');
|
|
145
|
-
return res.json();
|
|
146
|
-
}
|
|
147
|
-
```
|
|
148
|
-
|
|
149
|
-
### staleTime Reference
|
|
150
|
-
|
|
151
|
-
| Hook
|
|
152
|
-
|
|
153
|
-
| `useFeatureCards`
|
|
154
|
-
| `useAssistantConfig` | 30 min
|
|
155
|
-
|
|
156
|
-
> Library-internal showcase hooks (`useDashboardStats` — 5 min, `useTeamMembers` — 2 min) are not generated in the scaffold template but follow the same language-aware pattern.
|
|
157
|
-
|
|
158
|
-
---
|
|
159
|
-
|
|
160
|
-
## Zustand (Client UI State)
|
|
161
|
-
|
|
162
|
-
Zustand manages state that never needs to be fetched from a server — filters, tab selections, progress bars, switch toggles, and other UI controls.
|
|
163
|
-
|
|
164
|
-
### Writing a Store
|
|
165
|
-
|
|
166
|
-
```ts
|
|
167
|
-
// features/home/store/dashboardStore.ts
|
|
168
|
-
import { create } from 'zustand';
|
|
169
|
-
|
|
170
|
-
interface DashboardStore {
|
|
171
|
-
activeTab: string;
|
|
172
|
-
setActiveTab: (tab: string) => void;
|
|
173
|
-
progress: number;
|
|
174
|
-
setProgress: (value: number) => void;
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
export const useDashboardStore = create<DashboardStore>(set => ({
|
|
178
|
-
activeTab: 'overview',
|
|
179
|
-
setActiveTab: tab => set({ activeTab: tab }),
|
|
180
|
-
progress: 45,
|
|
181
|
-
setProgress: value => set({ progress: value }),
|
|
182
|
-
}));
|
|
183
|
-
```
|
|
184
|
-
|
|
185
|
-
### Consuming in a Component
|
|
186
|
-
|
|
187
|
-
```tsx
|
|
188
|
-
import { useDashboardStore } from '../../../features/home';
|
|
189
|
-
|
|
190
|
-
function ProgressSection() {
|
|
191
|
-
// Subscribe only to the slice you need — avoids unnecessary re-renders
|
|
192
|
-
const progress = useDashboardStore(s => s.progress);
|
|
193
|
-
const setProgress = useDashboardStore(s => s.setProgress);
|
|
194
|
-
|
|
195
|
-
return <Progress value={progress} onChange={setProgress} />;
|
|
196
|
-
}
|
|
197
|
-
```
|
|
198
|
-
|
|
199
|
-
### When to Use Zustand vs useState
|
|
200
|
-
|
|
201
|
-
| Use `useState`
|
|
202
|
-
|
|
203
|
-
| Dialog open/closed
|
|
204
|
-
| Rename input value
|
|
205
|
-
| Hover state
|
|
206
|
-
| Single-component toggle | Any state that must survive component unmount |
|
|
207
|
-
|
|
208
|
-
---
|
|
209
|
-
|
|
210
|
-
## AuthContext
|
|
211
|
-
|
|
212
|
-
Authentication state is managed by a dedicated context — not React Query and not Zustand.
|
|
213
|
-
|
|
214
|
-
### Setup
|
|
215
|
-
|
|
216
|
-
`AuthProvider` must be inside `<Router>` because it uses `useNavigate`:
|
|
217
|
-
|
|
218
|
-
```tsx
|
|
219
|
-
<Router>
|
|
220
|
-
<AuthProvider>
|
|
221
|
-
<AppRoutes />
|
|
222
|
-
</AuthProvider>
|
|
223
|
-
</Router>
|
|
224
|
-
```
|
|
225
|
-
|
|
226
|
-
### Hook
|
|
227
|
-
|
|
228
|
-
```tsx
|
|
229
|
-
import { useAuth } from '../app/context/AuthContext';
|
|
230
|
-
|
|
231
|
-
function MyComponent() {
|
|
232
|
-
const { user, isLoading, login, logout } = useAuth();
|
|
233
|
-
|
|
234
|
-
if (isLoading) return null; // waiting for localStorage hydration
|
|
235
|
-
|
|
236
|
-
return user ? (
|
|
237
|
-
<button onClick={logout}>Sign out {user.email}</button>
|
|
238
|
-
) : (
|
|
239
|
-
<button onClick={() => login('user@example.com', 'password')}>Sign in</button>
|
|
240
|
-
);
|
|
241
|
-
}
|
|
242
|
-
```
|
|
243
|
-
|
|
244
|
-
### `isLoading` Flag
|
|
245
|
-
|
|
246
|
-
On first render, `AuthProvider` reads `localStorage` asynchronously. Until it resolves, `isLoading` is `true`. Route guards check this flag before deciding to redirect:
|
|
247
|
-
|
|
248
|
-
```tsx
|
|
249
|
-
function ProtectedRoute({ children }) {
|
|
250
|
-
const { user, isLoading } = useAuth();
|
|
251
|
-
if (isLoading) return null;
|
|
252
|
-
if (!user) return <Navigate to="/login" replace />;
|
|
253
|
-
return <>{children}</>;
|
|
254
|
-
}
|
|
255
|
-
```
|
|
256
|
-
|
|
257
|
-
---
|
|
258
|
-
|
|
259
|
-
## Decision Tree
|
|
260
|
-
|
|
261
|
-
```
|
|
262
|
-
Need data from a server or async function?
|
|
263
|
-
└─ YES → React Query (useQuery in features/*/hooks/)
|
|
264
|
-
└─ Is it mock data? → Use features/*/data/mock.ts fetch function
|
|
265
|
-
└─ Is it a real API? → Replace fetch function body, keep everything else
|
|
266
|
-
|
|
267
|
-
State is browser-only (no server involved)?
|
|
268
|
-
└─ Shared across multiple components → Zustand (features/*/store/)
|
|
269
|
-
└─ Used by a single component → useState
|
|
270
|
-
|
|
271
|
-
Authentication / current user?
|
|
272
|
-
└─ useAuth() from AuthContext
|
|
273
|
-
|
|
274
|
-
Sidebar width / assistant panel?
|
|
275
|
-
└─ useLayout() from xertica-ui/hooks
|
|
276
|
-
```
|
|
277
|
-
|
|
278
|
-
---
|
|
279
|
-
|
|
280
|
-
## AI Rules
|
|
281
|
-
|
|
282
|
-
- **Never hardcode mock data in components** — all data arrays (team members, stats, feature cards, assistant suggestions) must live in `features/*/data/mock.ts`
|
|
283
|
-
- **Never call `fetch` directly inside a component** — always wrap in a React Query hook in `features/*/hooks/`
|
|
284
|
-
- **Never use `useState` for server data** — `useState([])` + `useEffect(() => fetch(...))` is the old pattern; use `useQuery` instead
|
|
285
|
-
- **`QueryClientProvider` goes outside `XerticaProvider`** — it must be the outermost provider so all nested providers can use React Query if needed
|
|
286
|
-
- **`AuthProvider` goes inside `<Router>`** — it uses `useNavigate` which requires a Router ancestor
|
|
287
|
-
- **Use `staleTime`** — always set a `staleTime` appropriate to how often the data changes; never leave it at the default `0` (which re-fetches on every focus)
|
|
288
|
-
- **Zustand selectors prevent re-renders** — always subscribe with a selector: `useDashboardStore(s => s.progress)` instead of `const store = useDashboardStore()`
|
|
289
|
-
- **`isLoading` from `useAuth()` prevents flash redirects** — always check `isLoading` in route guards before reading `user`
|
|
1
|
+
# State Management — Xertica UI
|
|
2
|
+
|
|
3
|
+
Projects scaffolded with `xertica-ui init` use a layered state strategy:
|
|
4
|
+
|
|
5
|
+
| Layer | Tool | Responsibility |
|
|
6
|
+
| ------------------------- | ------------------------------- | --------------------------------------------------------------------------------------- |
|
|
7
|
+
| **Server state** | TanStack React Query v5 | Data that originates from an API — fetching, caching, background refetch, loading/error |
|
|
8
|
+
| **Client UI state** | Zustand v5 | State that lives only in the browser — filters, toggles, form controls, selected tab |
|
|
9
|
+
| **Auth state** | `AuthContext` | Current user session, login, logout |
|
|
10
|
+
| **Layout state** | `LayoutContext` / `useLayout()` | Sidebar width/expanded, assistant expanded |
|
|
11
|
+
| **Local component state** | `useState` | Ephemeral state that does not need to outlive the component (dialogs, inline edits) |
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## Features Slice Structure
|
|
16
|
+
|
|
17
|
+
All server data and client UI state lives in `src/features/<domain>/`. The scaffold template (`npx xertica-ui init`) generates the following structure:
|
|
18
|
+
|
|
19
|
+
```
|
|
20
|
+
src/features/
|
|
21
|
+
├── home/
|
|
22
|
+
│ ├── data/
|
|
23
|
+
│ │ └── mock.ts ← types + mock data + async fetch functions
|
|
24
|
+
│ ├── hooks/
|
|
25
|
+
│ │ └── useFeatureCards.ts ← React Query hook (language-aware)
|
|
26
|
+
│ ├── store/
|
|
27
|
+
│ │ └── dashboardStore.ts ← Zustand store
|
|
28
|
+
│ ├── ui/
|
|
29
|
+
│ │ └── HomeContent.tsx ← page content component
|
|
30
|
+
│ └── index.ts ← barrel (re-exports everything)
|
|
31
|
+
└── assistant/
|
|
32
|
+
├── data/
|
|
33
|
+
│ └── mock.ts ← AssistantConfig + fetchAssistantConfig + factory functions
|
|
34
|
+
├── hooks/
|
|
35
|
+
│ └── useAssistantConfig.ts ← React Query hook (language-aware)
|
|
36
|
+
└── index.ts
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
> The library source (`xertica-ui` package) contains additional hooks (`useDashboardStats`, `useTeamMembers`) for the design-system showcase pages. These are not generated in the scaffold template.
|
|
40
|
+
|
|
41
|
+
### Adding a New Feature
|
|
42
|
+
|
|
43
|
+
1. Create `features/<name>/data/mock.ts` with your types and mock fetch functions
|
|
44
|
+
2. Create `features/<name>/hooks/use<Name>.ts` wrapping the fetch function in `useQuery`
|
|
45
|
+
3. Create `features/<name>/store/<name>Store.ts` for any client-side UI state
|
|
46
|
+
4. Re-export from `features/<name>/index.ts`
|
|
47
|
+
5. Consume in your component via the hook
|
|
48
|
+
|
|
49
|
+
---
|
|
50
|
+
|
|
51
|
+
## React Query (Server State)
|
|
52
|
+
|
|
53
|
+
### Setup
|
|
54
|
+
|
|
55
|
+
`QueryClientProvider` is placed at the root of the provider stack, outside `XerticaProvider`:
|
|
56
|
+
|
|
57
|
+
```tsx
|
|
58
|
+
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
|
59
|
+
|
|
60
|
+
const queryClient = new QueryClient({
|
|
61
|
+
defaultOptions: {
|
|
62
|
+
queries: {
|
|
63
|
+
retry: 1,
|
|
64
|
+
refetchOnWindowFocus: false,
|
|
65
|
+
},
|
|
66
|
+
},
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
function App() {
|
|
70
|
+
return (
|
|
71
|
+
<QueryClientProvider client={queryClient}>
|
|
72
|
+
<XerticaProvider>
|
|
73
|
+
<Router>...</Router>
|
|
74
|
+
</XerticaProvider>
|
|
75
|
+
</QueryClientProvider>
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### Writing a Hook
|
|
81
|
+
|
|
82
|
+
Hooks that return translated strings must include the active **language** in their `queryKey`. This gives each locale its own cache slot — switching language creates a new cache key, triggers a refetch in the new language, and switching back is an instant cache hit.
|
|
83
|
+
|
|
84
|
+
```ts
|
|
85
|
+
// features/home/hooks/useTeamMembers.ts
|
|
86
|
+
import { useQuery } from '@tanstack/react-query';
|
|
87
|
+
import { useLanguage } from 'xertica-ui/hooks';
|
|
88
|
+
import { fetchTeamMembers, type TeamMember } from '../data/mock';
|
|
89
|
+
|
|
90
|
+
export function useTeamMembers() {
|
|
91
|
+
const { language } = useLanguage(); // ← current locale from LanguageContext
|
|
92
|
+
return useQuery<TeamMember[]>({
|
|
93
|
+
queryKey: ['home', 'team-members', language], // ← language as third element
|
|
94
|
+
queryFn: fetchTeamMembers,
|
|
95
|
+
staleTime: 2 * 60 * 1000, // 2 min
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
> **Note:** Do not export a named `*_KEY` constant. The key now includes `language`, which is only available at hook call-time, making a module-level constant impossible. Keep the key inlined in the hook.
|
|
101
|
+
|
|
102
|
+
### Consuming in a Component
|
|
103
|
+
|
|
104
|
+
```tsx
|
|
105
|
+
import { useTeamMembers } from '../../../features/home';
|
|
106
|
+
|
|
107
|
+
export function TeamTable() {
|
|
108
|
+
const { data: members = [], isLoading, isError } = useTeamMembers();
|
|
109
|
+
|
|
110
|
+
if (isLoading) return <Skeleton />;
|
|
111
|
+
if (isError) return <ErrorState />;
|
|
112
|
+
|
|
113
|
+
return (
|
|
114
|
+
<Table>
|
|
115
|
+
<TableBody>
|
|
116
|
+
{members.map(m => (
|
|
117
|
+
<TableRow key={m.id}>
|
|
118
|
+
<TableCell>{m.name}</TableCell>
|
|
119
|
+
<TableCell>{m.email}</TableCell>
|
|
120
|
+
</TableRow>
|
|
121
|
+
))}
|
|
122
|
+
</TableBody>
|
|
123
|
+
</Table>
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
### Connecting to a Real API
|
|
129
|
+
|
|
130
|
+
Replace only the `fetch*` function in `data/mock.ts` — the hook and component stay unchanged:
|
|
131
|
+
|
|
132
|
+
```ts
|
|
133
|
+
// features/home/data/mock.ts
|
|
134
|
+
|
|
135
|
+
// Before (mock)
|
|
136
|
+
export async function fetchTeamMembers(): Promise<TeamMember[]> {
|
|
137
|
+
await new Promise(resolve => setTimeout(resolve, 250));
|
|
138
|
+
return MOCK_TEAM_MEMBERS;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// After (real API)
|
|
142
|
+
export async function fetchTeamMembers(): Promise<TeamMember[]> {
|
|
143
|
+
const res = await fetch('/api/team/members');
|
|
144
|
+
if (!res.ok) throw new Error('Failed to fetch team members');
|
|
145
|
+
return res.json();
|
|
146
|
+
}
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
### staleTime Reference
|
|
150
|
+
|
|
151
|
+
| Hook | staleTime | Rationale |
|
|
152
|
+
| -------------------- | --------- | ----------------------------- |
|
|
153
|
+
| `useFeatureCards` | 10 min | Feature list is nearly static |
|
|
154
|
+
| `useAssistantConfig` | 30 min | Config changes very rarely |
|
|
155
|
+
|
|
156
|
+
> Library-internal showcase hooks (`useDashboardStats` — 5 min, `useTeamMembers` — 2 min) are not generated in the scaffold template but follow the same language-aware pattern.
|
|
157
|
+
|
|
158
|
+
---
|
|
159
|
+
|
|
160
|
+
## Zustand (Client UI State)
|
|
161
|
+
|
|
162
|
+
Zustand manages state that never needs to be fetched from a server — filters, tab selections, progress bars, switch toggles, and other UI controls.
|
|
163
|
+
|
|
164
|
+
### Writing a Store
|
|
165
|
+
|
|
166
|
+
```ts
|
|
167
|
+
// features/home/store/dashboardStore.ts
|
|
168
|
+
import { create } from 'zustand';
|
|
169
|
+
|
|
170
|
+
interface DashboardStore {
|
|
171
|
+
activeTab: string;
|
|
172
|
+
setActiveTab: (tab: string) => void;
|
|
173
|
+
progress: number;
|
|
174
|
+
setProgress: (value: number) => void;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
export const useDashboardStore = create<DashboardStore>(set => ({
|
|
178
|
+
activeTab: 'overview',
|
|
179
|
+
setActiveTab: tab => set({ activeTab: tab }),
|
|
180
|
+
progress: 45,
|
|
181
|
+
setProgress: value => set({ progress: value }),
|
|
182
|
+
}));
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
### Consuming in a Component
|
|
186
|
+
|
|
187
|
+
```tsx
|
|
188
|
+
import { useDashboardStore } from '../../../features/home';
|
|
189
|
+
|
|
190
|
+
function ProgressSection() {
|
|
191
|
+
// Subscribe only to the slice you need — avoids unnecessary re-renders
|
|
192
|
+
const progress = useDashboardStore(s => s.progress);
|
|
193
|
+
const setProgress = useDashboardStore(s => s.setProgress);
|
|
194
|
+
|
|
195
|
+
return <Progress value={progress} onChange={setProgress} />;
|
|
196
|
+
}
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
### When to Use Zustand vs useState
|
|
200
|
+
|
|
201
|
+
| Use `useState` | Use `useDashboardStore` (Zustand) |
|
|
202
|
+
| ----------------------- | --------------------------------------------- |
|
|
203
|
+
| Dialog open/closed | Active filter tab |
|
|
204
|
+
| Rename input value | Slider value shared across sections |
|
|
205
|
+
| Hover state | Switch enabled state |
|
|
206
|
+
| Single-component toggle | Any state that must survive component unmount |
|
|
207
|
+
|
|
208
|
+
---
|
|
209
|
+
|
|
210
|
+
## AuthContext
|
|
211
|
+
|
|
212
|
+
Authentication state is managed by a dedicated context — not React Query and not Zustand.
|
|
213
|
+
|
|
214
|
+
### Setup
|
|
215
|
+
|
|
216
|
+
`AuthProvider` must be inside `<Router>` because it uses `useNavigate`:
|
|
217
|
+
|
|
218
|
+
```tsx
|
|
219
|
+
<Router>
|
|
220
|
+
<AuthProvider>
|
|
221
|
+
<AppRoutes />
|
|
222
|
+
</AuthProvider>
|
|
223
|
+
</Router>
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
### Hook
|
|
227
|
+
|
|
228
|
+
```tsx
|
|
229
|
+
import { useAuth } from '../app/context/AuthContext';
|
|
230
|
+
|
|
231
|
+
function MyComponent() {
|
|
232
|
+
const { user, isLoading, login, logout } = useAuth();
|
|
233
|
+
|
|
234
|
+
if (isLoading) return null; // waiting for localStorage hydration
|
|
235
|
+
|
|
236
|
+
return user ? (
|
|
237
|
+
<button onClick={logout}>Sign out {user.email}</button>
|
|
238
|
+
) : (
|
|
239
|
+
<button onClick={() => login('user@example.com', 'password')}>Sign in</button>
|
|
240
|
+
);
|
|
241
|
+
}
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
### `isLoading` Flag
|
|
245
|
+
|
|
246
|
+
On first render, `AuthProvider` reads `localStorage` asynchronously. Until it resolves, `isLoading` is `true`. Route guards check this flag before deciding to redirect:
|
|
247
|
+
|
|
248
|
+
```tsx
|
|
249
|
+
function ProtectedRoute({ children }) {
|
|
250
|
+
const { user, isLoading } = useAuth();
|
|
251
|
+
if (isLoading) return null; // render nothing until hydrated
|
|
252
|
+
if (!user) return <Navigate to="/login" replace />;
|
|
253
|
+
return <>{children}</>;
|
|
254
|
+
}
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
---
|
|
258
|
+
|
|
259
|
+
## Decision Tree
|
|
260
|
+
|
|
261
|
+
```
|
|
262
|
+
Need data from a server or async function?
|
|
263
|
+
└─ YES → React Query (useQuery in features/*/hooks/)
|
|
264
|
+
└─ Is it mock data? → Use features/*/data/mock.ts fetch function
|
|
265
|
+
└─ Is it a real API? → Replace fetch function body, keep everything else
|
|
266
|
+
|
|
267
|
+
State is browser-only (no server involved)?
|
|
268
|
+
└─ Shared across multiple components → Zustand (features/*/store/)
|
|
269
|
+
└─ Used by a single component → useState
|
|
270
|
+
|
|
271
|
+
Authentication / current user?
|
|
272
|
+
└─ useAuth() from AuthContext
|
|
273
|
+
|
|
274
|
+
Sidebar width / assistant panel?
|
|
275
|
+
└─ useLayout() from xertica-ui/hooks
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
---
|
|
279
|
+
|
|
280
|
+
## AI Rules
|
|
281
|
+
|
|
282
|
+
- **Never hardcode mock data in components** — all data arrays (team members, stats, feature cards, assistant suggestions) must live in `features/*/data/mock.ts`
|
|
283
|
+
- **Never call `fetch` directly inside a component** — always wrap in a React Query hook in `features/*/hooks/`
|
|
284
|
+
- **Never use `useState` for server data** — `useState([])` + `useEffect(() => fetch(...))` is the old pattern; use `useQuery` instead
|
|
285
|
+
- **`QueryClientProvider` goes outside `XerticaProvider`** — it must be the outermost provider so all nested providers can use React Query if needed
|
|
286
|
+
- **`AuthProvider` goes inside `<Router>`** — it uses `useNavigate` which requires a Router ancestor
|
|
287
|
+
- **Use `staleTime`** — always set a `staleTime` appropriate to how often the data changes; never leave it at the default `0` (which re-fetches on every focus)
|
|
288
|
+
- **Zustand selectors prevent re-renders** — always subscribe with a selector: `useDashboardStore(s => s.progress)` instead of `const store = useDashboardStore()`
|
|
289
|
+
- **`isLoading` from `useAuth()` prevents flash redirects** — always check `isLoading` in route guards before reading `user`
|