@wakastellar/ui 2.3.4 → 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.
Files changed (194) hide show
  1. package/dist/blocks/dashboard/index.d.ts +4 -1
  2. package/dist/blocks/empty-states/index.d.ts +4 -1
  3. package/dist/blocks/error-pages/index.d.ts +4 -1
  4. package/dist/blocks/index.d.ts +1 -1
  5. package/dist/blocks/landing/index.d.ts +4 -1
  6. package/dist/blocks/pricing/index.d.ts +5 -1
  7. package/dist/blocks/sidebar/index.d.ts +5 -1
  8. package/dist/index.cjs.js +130 -130
  9. package/dist/index.d.ts +1 -0
  10. package/dist/index.es.js +7905 -7856
  11. package/dist/stories/Button.d.ts +14 -0
  12. package/dist/stories/Button.stories.d.ts +8 -0
  13. package/dist/stories/Header.d.ts +11 -0
  14. package/dist/stories/Header.stories.d.ts +6 -0
  15. package/dist/stories/Page.d.ts +2 -0
  16. package/dist/stories/Page.stories.d.ts +6 -0
  17. package/dist/types/index.d.ts +1 -0
  18. package/dist/types/link.d.ts +23 -0
  19. package/package.json +11 -3
  20. package/src/blocks/activity-timeline/ActivityTimeline.stories.tsx +460 -0
  21. package/src/blocks/apm-overview/APMOverview.stories.tsx +435 -0
  22. package/src/blocks/auth-2fa/Auth2FA.stories.tsx +308 -0
  23. package/src/blocks/calendar-view/CalendarView.stories.tsx +398 -0
  24. package/src/blocks/chat/Chat.stories.tsx +466 -0
  25. package/src/blocks/chat-interface/ChatInterface.stories.tsx +412 -0
  26. package/src/blocks/checkout-flow/CheckoutFlow.stories.tsx +408 -0
  27. package/src/blocks/cicd-builder/CICDBuilder.stories.tsx +499 -0
  28. package/src/blocks/cloud-cost-dashboard/CloudCostDashboard.stories.tsx +356 -0
  29. package/src/blocks/container-orchestrator/ContainerOrchestrator.stories.tsx +461 -0
  30. package/src/blocks/dashboard/Dashboard.stories.tsx +559 -0
  31. package/src/blocks/dashboard/index.tsx +68 -27
  32. package/src/blocks/dashboard-kpi/DashboardKPI.stories.tsx +422 -0
  33. package/src/blocks/database-admin/DatabaseAdmin.stories.tsx +393 -0
  34. package/src/blocks/deployment-dashboard/DeploymentDashboard.stories.tsx +457 -0
  35. package/src/blocks/empty-states/EmptyStates.stories.tsx +467 -0
  36. package/src/blocks/empty-states/index.tsx +26 -8
  37. package/src/blocks/error-pages/ErrorPages.stories.tsx +401 -0
  38. package/src/blocks/error-pages/index.tsx +26 -8
  39. package/src/blocks/faq/FAQ.stories.tsx +416 -0
  40. package/src/blocks/file-manager/FileManager.stories.tsx +413 -0
  41. package/src/blocks/footer/Footer.stories.tsx +328 -0
  42. package/src/blocks/gitops-sync-status/GitOpsSyncStatus.stories.tsx +529 -0
  43. package/src/blocks/header/WakaHeader.stories.tsx +455 -0
  44. package/src/blocks/headtab/Headtab.stories.tsx +369 -0
  45. package/src/blocks/i18n-editor/I18nEditor.stories.tsx +451 -0
  46. package/src/blocks/incident-manager/IncidentManager.stories.tsx +464 -0
  47. package/src/blocks/index.ts +1 -1
  48. package/src/blocks/infrastructure-map/InfrastructureMap.stories.tsx +533 -0
  49. package/src/blocks/kanban-board/KanbanBoard.stories.tsx +494 -0
  50. package/src/blocks/landing/WakaLanding.stories.tsx +449 -0
  51. package/src/blocks/landing/index.tsx +125 -66
  52. package/src/blocks/language-selector/LanguageSelector.stories.tsx +345 -0
  53. package/src/blocks/layout/Layout.stories.tsx +373 -0
  54. package/src/blocks/login/Login.stories.tsx +443 -0
  55. package/src/blocks/on-call-schedule/OnCallSchedule.stories.tsx +381 -0
  56. package/src/blocks/player-profile/PlayerProfile.stories.tsx +316 -0
  57. package/src/blocks/pricing/WakaPricing.stories.tsx +530 -0
  58. package/src/blocks/pricing/index.tsx +38 -4
  59. package/src/blocks/profile/Profile.stories.tsx +371 -0
  60. package/src/blocks/release-notes/ReleaseNotes.stories.tsx +431 -0
  61. package/src/blocks/settings/Settings.stories.tsx +520 -0
  62. package/src/blocks/sidebar/WakaSidebar.stories.tsx +513 -0
  63. package/src/blocks/sidebar/index.tsx +49 -20
  64. package/src/blocks/theme-creator-block/ThemeCreatorBlock.stories.tsx +370 -0
  65. package/src/blocks/user-management/UserManagement.stories.tsx +411 -0
  66. package/src/blocks/wizard/Wizard.stories.tsx +683 -0
  67. package/src/components/accordion/Accordion.stories.tsx +93 -0
  68. package/src/components/alert/Alert.stories.tsx +95 -0
  69. package/src/components/alert-dialog/AlertDialog.stories.tsx +126 -0
  70. package/src/components/aspect-ratio/AspectRatio.stories.tsx +118 -0
  71. package/src/components/avatar/Avatar.stories.tsx +104 -0
  72. package/src/components/button/Button.stories.tsx +12 -1
  73. package/src/components/calendar/Calendar.stories.tsx +102 -0
  74. package/src/components/card/Card.stories.tsx +125 -0
  75. package/src/components/checkbox/Checkbox.stories.tsx +100 -0
  76. package/src/components/code/Code.stories.tsx +402 -0
  77. package/src/components/collapsible/Collapsible.stories.tsx +123 -0
  78. package/src/components/command/Command.stories.tsx +207 -0
  79. package/src/components/context-menu/ContextMenu.stories.tsx +220 -0
  80. package/src/components/dialog/Dialog.stories.tsx +157 -0
  81. package/src/components/dropdown-menu/DropdownMenu.stories.tsx +225 -0
  82. package/src/components/form/Form.stories.tsx +413 -0
  83. package/src/components/hover-card/HoverCard.stories.tsx +148 -0
  84. package/src/components/input-otp/InputOTP.stories.tsx +255 -0
  85. package/src/components/label/Label.stories.tsx +68 -0
  86. package/src/components/menubar/Menubar.stories.tsx +278 -0
  87. package/src/components/navigation-menu/NavigationMenu.stories.tsx +202 -0
  88. package/src/components/popover/Popover.stories.tsx +199 -0
  89. package/src/components/progress/Progress.stories.tsx +104 -0
  90. package/src/components/radio-group/RadioGroup.stories.tsx +138 -0
  91. package/src/components/scroll-area/ScrollArea.stories.tsx +153 -0
  92. package/src/components/select/Select.stories.tsx +146 -0
  93. package/src/components/separator/Separator.stories.tsx +117 -0
  94. package/src/components/sheet/Sheet.stories.tsx +195 -0
  95. package/src/components/skeleton/Skeleton.stories.tsx +114 -0
  96. package/src/components/slider/Slider.stories.tsx +157 -0
  97. package/src/components/switch/Switch.stories.tsx +114 -0
  98. package/src/components/table/Table.stories.tsx +153 -0
  99. package/src/components/tabs/Tabs.stories.tsx +155 -0
  100. package/src/components/textarea/Textarea.stories.tsx +116 -0
  101. package/src/components/toast/Toast.stories.tsx +160 -0
  102. package/src/components/toggle/Toggle.stories.tsx +125 -0
  103. package/src/components/tooltip/Tooltip.stories.tsx +133 -0
  104. package/src/components/typography/Typography.stories.tsx +207 -0
  105. package/src/components/waka-3d-pie-chart/Waka3DPieChart.stories.tsx +308 -0
  106. package/src/components/waka-achievement-unlock/WakaAchievementUnlock.stories.tsx +353 -0
  107. package/src/components/waka-artifact-list/WakaArtifactList.stories.tsx +258 -0
  108. package/src/components/waka-autocomplete/WakaAutocomplete.stories.tsx +224 -0
  109. package/src/components/waka-badge-showcase/WakaBadgeShowcase.stories.tsx +269 -0
  110. package/src/components/waka-barcode/WakaBarcode.stories.tsx +227 -0
  111. package/src/components/waka-bottom-sheet/WakaBottomSheet.stories.tsx +408 -0
  112. package/src/components/waka-breadcrumb/WakaBreadcrumb.stories.tsx +237 -0
  113. package/src/components/waka-build-matrix/WakaBuildMatrix.stories.tsx +328 -0
  114. package/src/components/waka-carousel/WakaCarousel.stories.tsx +296 -0
  115. package/src/components/waka-charts/WakaCharts.stories.tsx +519 -0
  116. package/src/components/waka-color-picker/WakaColorPicker.stories.tsx +200 -0
  117. package/src/components/waka-combobox/WakaCombobox.stories.tsx +250 -0
  118. package/src/components/waka-container-list/WakaContainerList.stories.tsx +315 -0
  119. package/src/components/waka-contribution-graph/WakaContributionGraph.stories.tsx +354 -0
  120. package/src/components/waka-cost-breakdown/WakaCostBreakdown.stories.tsx +348 -0
  121. package/src/components/waka-daily-reward/WakaDailyReward.stories.tsx +365 -0
  122. package/src/components/waka-database-card/WakaDatabaseCard.stories.tsx +306 -0
  123. package/src/components/waka-date-range-picker/WakaDateRangePicker.stories.tsx +339 -0
  124. package/src/components/waka-datetime-picker/WakaDateTimePicker.stories.tsx +317 -0
  125. package/src/components/waka-deployment-lane/WakaDeploymentLane.stories.tsx +292 -0
  126. package/src/components/waka-dock/WakaDock.stories.tsx +332 -0
  127. package/src/components/waka-drawer/WakaDrawer.stories.tsx +437 -0
  128. package/src/components/waka-env-var-editor/WakaEnvVarEditor.stories.tsx +263 -0
  129. package/src/components/waka-error-shake/WakaErrorShake.stories.tsx +410 -0
  130. package/src/components/waka-file-upload/WakaFileUpload.stories.tsx +239 -0
  131. package/src/components/waka-flow-diagram/WakaFlowDiagram.stories.tsx +365 -0
  132. package/src/components/waka-funnel-chart/WakaFunnelChart.stories.tsx +281 -0
  133. package/src/components/waka-glow-card/WakaGlowCard.stories.tsx +274 -0
  134. package/src/components/waka-haptic-button/WakaHapticButton.stories.tsx +349 -0
  135. package/src/components/waka-health-pulse/WakaHealthPulse.stories.tsx +293 -0
  136. package/src/components/waka-heatmap/WakaHeatmap.stories.tsx +376 -0
  137. package/src/components/waka-image/WakaImage.stories.tsx +255 -0
  138. package/src/components/waka-incident-timeline/WakaIncidentTimeline.stories.tsx +300 -0
  139. package/src/components/waka-kanban/WakaKanban.stories.tsx +399 -0
  140. package/src/components/waka-kubernetes-overview/WakaKubernetesOverview.stories.tsx +281 -0
  141. package/src/components/waka-leaderboard/WakaLeaderboard.stories.tsx +300 -0
  142. package/src/components/waka-level-progress/WakaLevelProgress.stories.tsx +313 -0
  143. package/src/components/waka-loading-orbit/WakaLoadingOrbit.stories.tsx +413 -0
  144. package/src/components/waka-log-viewer/WakaLogViewer.stories.tsx +312 -0
  145. package/src/components/waka-loot-box/WakaLootBox.stories.tsx +374 -0
  146. package/src/components/waka-metric-sparkline/WakaMetricSparkline.stories.tsx +312 -0
  147. package/src/components/waka-migration-list/WakaMigrationList.stories.tsx +289 -0
  148. package/src/components/waka-modal/WakaModal.stories.tsx +434 -0
  149. package/src/components/waka-morph-button/WakaMorphButton.stories.tsx +405 -0
  150. package/src/components/waka-network-topology/WakaNetworkTopology.stories.tsx +364 -0
  151. package/src/components/waka-notifications/WakaNotifications.stories.tsx +290 -0
  152. package/src/components/waka-number-input/WakaNumberInput.stories.tsx +282 -0
  153. package/src/components/waka-pagination/WakaPagination.stories.tsx +328 -0
  154. package/src/components/waka-password-strength/WakaPasswordStrength.stories.tsx +318 -0
  155. package/src/components/waka-pipeline-view/WakaPipelineView.stories.tsx +386 -0
  156. package/src/components/waka-player-card/WakaPlayerCard.stories.tsx +333 -0
  157. package/src/components/waka-pod-card/WakaPodCard.stories.tsx +435 -0
  158. package/src/components/waka-qrcode/WakaQRCode.stories.tsx +232 -0
  159. package/src/components/waka-query-explain/WakaQueryExplain.stories.tsx +407 -0
  160. package/src/components/waka-quest-card/WakaQuestCard.stories.tsx +394 -0
  161. package/src/components/waka-quota-bar/WakaQuotaBar.stories.tsx +435 -0
  162. package/src/components/waka-radar-score/WakaRadarScore.stories.tsx +372 -0
  163. package/src/components/waka-resource-gauge/WakaResourceGauge.stories.tsx +366 -0
  164. package/src/components/waka-rich-text-editor/WakaRichTextEditor.stories.tsx +238 -0
  165. package/src/components/waka-sankey-diagram/WakaSankeyDiagram.stories.tsx +389 -0
  166. package/src/components/waka-scratch-card/WakaScratchCard.stories.tsx +388 -0
  167. package/src/components/waka-secret-card/WakaSecretCard.stories.tsx +314 -0
  168. package/src/components/waka-segmented-control/WakaSegmentedControl.stories.tsx +309 -0
  169. package/src/components/waka-server-rack/WakaServerRack.stories.tsx +382 -0
  170. package/src/components/waka-service-graph/WakaServiceGraph.stories.tsx +262 -0
  171. package/src/components/waka-skeleton-wave/WakaSkeletonWave.stories.tsx +321 -0
  172. package/src/components/waka-skill-tree/WakaSkillTree.stories.tsx +308 -0
  173. package/src/components/waka-spin-wheel/WakaSpinWheel.stories.tsx +368 -0
  174. package/src/components/waka-spinner/WakaSpinner.stories.tsx +156 -0
  175. package/src/components/waka-stat/WakaStat.stories.tsx +334 -0
  176. package/src/components/waka-status-matrix/WakaStatusMatrix.stories.tsx +331 -0
  177. package/src/components/waka-stepper/WakaStepper.stories.tsx +468 -0
  178. package/src/components/waka-streak-counter/WakaStreakCounter.stories.tsx +235 -0
  179. package/src/components/waka-success-explosion/WakaSuccessExplosion.stories.tsx +389 -0
  180. package/src/components/waka-tabs-morph/WakaTabsMorph.stories.tsx +471 -0
  181. package/src/components/waka-terminal-output/WakaTerminalOutput.stories.tsx +351 -0
  182. package/src/components/waka-test-report/WakaTestReport.stories.tsx +322 -0
  183. package/src/components/waka-tilt-card/WakaTiltCard.stories.tsx +300 -0
  184. package/src/components/waka-time-picker/WakaTimePicker.stories.tsx +227 -0
  185. package/src/components/waka-timeline/WakaTimeline.stories.tsx +383 -0
  186. package/src/components/waka-tournament-bracket/WakaTournamentBracket.stories.tsx +375 -0
  187. package/src/components/waka-trace-viewer/WakaTraceViewer.stories.tsx +445 -0
  188. package/src/components/waka-tree/WakaTree.stories.tsx +359 -0
  189. package/src/components/waka-treemap-chart/WakaTreemapChart.stories.tsx +378 -0
  190. package/src/components/waka-typewriter/WakaTypewriter.stories.tsx +366 -0
  191. package/src/components/waka-versus-card/WakaVersusCard.stories.tsx +530 -0
  192. package/src/components/waka-video/WakaVideo.stories.tsx +203 -0
  193. package/src/components/waka-virtual-list/WakaVirtualList.stories.tsx +273 -0
  194. package/src/components/waka-xp-bar/WakaXPBar.stories.tsx +305 -0
@@ -2,6 +2,7 @@
2
2
 
3
3
  import * as React from "react"
4
4
  import { cn } from "../../utils"
5
+ import type { LinkComponentProps } from "../../types/link"
5
6
  import { Button } from "../../components/button"
6
7
  import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "../../components/card"
7
8
  import { Badge } from "../../components/badge"
@@ -117,6 +118,22 @@ export interface WakaLandingProps {
117
118
  footer?: React.ReactNode
118
119
  /** Classes CSS additionnelles */
119
120
  className?: string
121
+ /** Composant de lien personnalisé (ex: next/link) pour navigation SPA */
122
+ LinkComponent?: React.ComponentType<LinkComponentProps>
123
+ }
124
+
125
+ // ============================================
126
+ // CONTEXT
127
+ // ============================================
128
+
129
+ interface LandingContextValue {
130
+ LinkComponent?: React.ComponentType<LinkComponentProps>
131
+ }
132
+
133
+ const LandingContext = React.createContext<LandingContextValue>({})
134
+
135
+ function useLandingContext() {
136
+ return React.useContext(LandingContext)
120
137
  }
121
138
 
122
139
  // ============================================
@@ -143,29 +160,35 @@ function LandingNavbar({
143
160
  loginHref,
144
161
  }: LandingNavbarProps) {
145
162
  const [isOpen, setIsOpen] = React.useState(false)
163
+ const { LinkComponent } = useLandingContext()
164
+
165
+ const renderLink = (href: string, className: string, children: React.ReactNode, onClick?: () => void) => {
166
+ if (LinkComponent) {
167
+ return <LinkComponent href={href} className={className} onClick={onClick}>{children}</LinkComponent>
168
+ }
169
+ return <a href={href} className={className} onClick={onClick}>{children}</a>
170
+ }
146
171
 
147
172
  return (
148
173
  <nav className="sticky top-0 z-50 w-full border-b bg-background/95 backdrop-blur supports-[backdrop-filter]:bg-background/60">
149
174
  <div className="container flex h-16 items-center justify-between">
150
175
  {/* Logo */}
151
- <a href="/" className="flex items-center gap-2 font-bold text-xl">
176
+ {renderLink("/", "flex items-center gap-2 font-bold text-xl", <>
152
177
  {logo}
153
178
  <span>{brandName}</span>
154
- </a>
179
+ </>)}
155
180
 
156
181
  {/* Desktop Nav */}
157
182
  <div className="hidden md:flex items-center gap-6">
158
183
  {navItems.map((item) => (
159
- <a
160
- key={item.href}
161
- href={item.href}
162
- className={cn(
184
+ renderLink(
185
+ item.href,
186
+ cn(
163
187
  "text-sm font-medium transition-colors hover:text-primary",
164
188
  item.isActive ? "text-primary" : "text-muted-foreground"
165
- )}
166
- >
167
- {item.label}
168
- </a>
189
+ ),
190
+ item.label
191
+ )
169
192
  ))}
170
193
  </div>
171
194
 
@@ -178,7 +201,11 @@ function LandingNavbar({
178
201
  asChild={!!loginHref}
179
202
  >
180
203
  {loginHref ? (
181
- <a href={loginHref}>{loginButtonText}</a>
204
+ LinkComponent ? (
205
+ <LinkComponent href={loginHref}>{loginButtonText}</LinkComponent>
206
+ ) : (
207
+ <a href={loginHref}>{loginButtonText}</a>
208
+ )
182
209
  ) : (
183
210
  loginButtonText
184
211
  )}
@@ -202,19 +229,21 @@ function LandingNavbar({
202
229
  <div className="md:hidden border-t bg-background p-4">
203
230
  <div className="flex flex-col gap-4">
204
231
  {navItems.map((item) => (
205
- <a
206
- key={item.href}
207
- href={item.href}
208
- className="text-sm font-medium"
209
- onClick={() => setIsOpen(false)}
210
- >
211
- {item.label}
212
- </a>
232
+ renderLink(
233
+ item.href,
234
+ "text-sm font-medium",
235
+ item.label,
236
+ () => setIsOpen(false)
237
+ )
213
238
  ))}
214
239
  {showLoginButton && (
215
240
  <Button onClick={onLogin} asChild={!!loginHref}>
216
241
  {loginHref ? (
217
- <a href={loginHref}>{loginButtonText}</a>
242
+ LinkComponent ? (
243
+ <LinkComponent href={loginHref}>{loginButtonText}</LinkComponent>
244
+ ) : (
245
+ <a href={loginHref}>{loginButtonText}</a>
246
+ )
218
247
  ) : (
219
248
  loginButtonText
220
249
  )}
@@ -243,6 +272,8 @@ function LandingHeroSection({
243
272
  image,
244
273
  stats,
245
274
  }: LandingHeroSectionProps) {
275
+ const { LinkComponent } = useLandingContext()
276
+
246
277
  return (
247
278
  <section className="container py-24 md:py-32">
248
279
  <div className="flex flex-col lg:flex-row items-center gap-12">
@@ -272,10 +303,17 @@ function LandingHeroSection({
272
303
  asChild={!!primaryButtonHref}
273
304
  >
274
305
  {primaryButtonHref ? (
275
- <a href={primaryButtonHref}>
276
- {primaryButtonText}
277
- <ArrowRight className="ml-2 h-4 w-4" />
278
- </a>
306
+ LinkComponent ? (
307
+ <LinkComponent href={primaryButtonHref}>
308
+ {primaryButtonText}
309
+ <ArrowRight className="ml-2 h-4 w-4" />
310
+ </LinkComponent>
311
+ ) : (
312
+ <a href={primaryButtonHref}>
313
+ {primaryButtonText}
314
+ <ArrowRight className="ml-2 h-4 w-4" />
315
+ </a>
316
+ )
279
317
  ) : (
280
318
  <>
281
319
  {primaryButtonText}
@@ -292,10 +330,17 @@ function LandingHeroSection({
292
330
  asChild={!!secondaryButtonHref}
293
331
  >
294
332
  {secondaryButtonHref ? (
295
- <a href={secondaryButtonHref}>
296
- <Play className="mr-2 h-4 w-4" />
297
- {secondaryButtonText}
298
- </a>
333
+ LinkComponent ? (
334
+ <LinkComponent href={secondaryButtonHref}>
335
+ <Play className="mr-2 h-4 w-4" />
336
+ {secondaryButtonText}
337
+ </LinkComponent>
338
+ ) : (
339
+ <a href={secondaryButtonHref}>
340
+ <Play className="mr-2 h-4 w-4" />
341
+ {secondaryButtonText}
342
+ </a>
343
+ )
299
344
  ) : (
300
345
  <>
301
346
  <Play className="mr-2 h-4 w-4" />
@@ -459,6 +504,8 @@ function LandingCTASection({
459
504
  secondaryButtonAction,
460
505
  variant = "default",
461
506
  }: LandingCTASectionProps) {
507
+ const { LinkComponent } = useLandingContext()
508
+
462
509
  const variantClasses = {
463
510
  default: "bg-muted/50",
464
511
  gradient: "bg-gradient-to-r from-primary/10 via-primary/5 to-background",
@@ -495,10 +542,17 @@ function LandingCTASection({
495
542
  asChild={!!buttonHref}
496
543
  >
497
544
  {buttonHref ? (
498
- <a href={buttonHref}>
499
- {buttonText}
500
- <ChevronRight className="ml-2 h-4 w-4" />
501
- </a>
545
+ LinkComponent ? (
546
+ <LinkComponent href={buttonHref}>
547
+ {buttonText}
548
+ <ChevronRight className="ml-2 h-4 w-4" />
549
+ </LinkComponent>
550
+ ) : (
551
+ <a href={buttonHref}>
552
+ {buttonText}
553
+ <ChevronRight className="ml-2 h-4 w-4" />
554
+ </a>
555
+ )
502
556
  ) : (
503
557
  <>
504
558
  {buttonText}
@@ -544,46 +598,51 @@ export function WakaLanding({
544
598
  cta,
545
599
  footer,
546
600
  className,
601
+ LinkComponent,
547
602
  }: WakaLandingProps) {
603
+ const contextValue = React.useMemo(() => ({ LinkComponent }), [LinkComponent])
604
+
548
605
  return (
549
- <div className={cn("min-h-screen", className)}>
550
- {/* Navbar */}
551
- <LandingNavbar
552
- logo={logo}
553
- brandName={brandName}
554
- navItems={navItems}
555
- showLoginButton={showLoginButton}
556
- loginButtonText={loginButtonText}
557
- onLogin={onLogin}
558
- loginHref={loginHref}
559
- />
560
-
561
- {/* Hero */}
562
- {hero && <LandingHeroSection {...hero} />}
563
-
564
- {/* Features */}
565
- {features && features.length > 0 && (
566
- <LandingFeaturesSection
567
- title={featuresTitle}
568
- description={featuresDescription}
569
- features={features}
606
+ <LandingContext.Provider value={contextValue}>
607
+ <div className={cn("min-h-screen", className)}>
608
+ {/* Navbar */}
609
+ <LandingNavbar
610
+ logo={logo}
611
+ brandName={brandName}
612
+ navItems={navItems}
613
+ showLoginButton={showLoginButton}
614
+ loginButtonText={loginButtonText}
615
+ onLogin={onLogin}
616
+ loginHref={loginHref}
570
617
  />
571
- )}
572
618
 
573
- {/* Testimonials */}
574
- {testimonials && testimonials.length > 0 && (
575
- <LandingTestimonialsSection
576
- title={testimonialsTitle}
577
- testimonials={testimonials}
578
- />
579
- )}
619
+ {/* Hero */}
620
+ {hero && <LandingHeroSection {...hero} />}
580
621
 
581
- {/* CTA */}
582
- {cta && <LandingCTASection {...cta} />}
622
+ {/* Features */}
623
+ {features && features.length > 0 && (
624
+ <LandingFeaturesSection
625
+ title={featuresTitle}
626
+ description={featuresDescription}
627
+ features={features}
628
+ />
629
+ )}
583
630
 
584
- {/* Footer */}
585
- {footer}
586
- </div>
631
+ {/* Testimonials */}
632
+ {testimonials && testimonials.length > 0 && (
633
+ <LandingTestimonialsSection
634
+ title={testimonialsTitle}
635
+ testimonials={testimonials}
636
+ />
637
+ )}
638
+
639
+ {/* CTA */}
640
+ {cta && <LandingCTASection {...cta} />}
641
+
642
+ {/* Footer */}
643
+ {footer}
644
+ </div>
645
+ </LandingContext.Provider>
587
646
  )
588
647
  }
589
648
 
@@ -0,0 +1,345 @@
1
+ import type { Meta, StoryObj } from '@storybook/react'
2
+ import { LanguageSelector } from './index'
3
+ import type { LanguageConfig } from '../../context/language-context'
4
+ import * as React from 'react'
5
+
6
+ // Mock the useLanguage hook since we can't use the real provider in Storybook
7
+ // We need to create a wrapper that provides the context
8
+
9
+ const mockLanguages: LanguageConfig[] = [
10
+ { code: 'fr', label: 'Français', flagEmoji: '🇫🇷' },
11
+ { code: 'en', label: 'English', flagEmoji: '🇬🇧' },
12
+ { code: 'es', label: 'Español', flagEmoji: '🇪🇸' },
13
+ { code: 'de', label: 'Deutsch', flagEmoji: '🇩🇪' },
14
+ { code: 'it', label: 'Italiano', flagEmoji: '🇮🇹' },
15
+ { code: 'pt', label: 'Português', flagEmoji: '🇵🇹' },
16
+ { code: 'ja', label: '日本語', flagEmoji: '🇯🇵' },
17
+ { code: 'zh', label: '中文', flagEmoji: '🇨🇳' },
18
+ ]
19
+
20
+ // Create a mock context for storybook demos
21
+ const MockLanguageContext = React.createContext<{
22
+ currentLanguage: string
23
+ languages: LanguageConfig[]
24
+ changeLanguage: (code: string) => void
25
+ isLoading: boolean
26
+ } | null>(null)
27
+
28
+ function MockLanguageProvider({
29
+ children,
30
+ initialLanguage = 'fr',
31
+ languages = mockLanguages.slice(0, 4),
32
+ }: {
33
+ children: React.ReactNode
34
+ initialLanguage?: string
35
+ languages?: LanguageConfig[]
36
+ }) {
37
+ const [currentLanguage, setCurrentLanguage] = React.useState(initialLanguage)
38
+ const [isLoading, setIsLoading] = React.useState(false)
39
+
40
+ const changeLanguage = React.useCallback((code: string) => {
41
+ setIsLoading(true)
42
+ // Simulate async language change
43
+ setTimeout(() => {
44
+ setCurrentLanguage(code)
45
+ setIsLoading(false)
46
+ console.log('Language changed to:', code)
47
+ }, 500)
48
+ }, [])
49
+
50
+ return (
51
+ <MockLanguageContext.Provider value={{ currentLanguage, languages, changeLanguage, isLoading }}>
52
+ {children}
53
+ </MockLanguageContext.Provider>
54
+ )
55
+ }
56
+
57
+ // Mock LanguageSelector that uses our mock context
58
+ function MockLanguageSelector({
59
+ variant = 'dropdown',
60
+ showFlags = true,
61
+ className,
62
+ }: {
63
+ variant?: 'dropdown' | 'select'
64
+ showFlags?: boolean
65
+ className?: string
66
+ }) {
67
+ const context = React.useContext(MockLanguageContext)
68
+
69
+ if (!context) {
70
+ return <div className="text-red-500">Error: Missing MockLanguageProvider</div>
71
+ }
72
+
73
+ const { currentLanguage, languages, changeLanguage, isLoading } = context
74
+ const currentLang = languages.find(lang => lang.code === currentLanguage)
75
+
76
+ if (variant === 'select') {
77
+ return (
78
+ <div className={className}>
79
+ <select
80
+ value={currentLanguage}
81
+ onChange={(e) => changeLanguage(e.target.value)}
82
+ disabled={isLoading}
83
+ className="h-10 px-3 py-2 border rounded-md bg-background text-sm min-w-[180px]"
84
+ >
85
+ {languages.map((lang) => (
86
+ <option key={lang.code} value={lang.code}>
87
+ {showFlags && lang.flagEmoji ? `${lang.flagEmoji} ` : ''}{lang.label}
88
+ </option>
89
+ ))}
90
+ </select>
91
+ </div>
92
+ )
93
+ }
94
+
95
+ return (
96
+ <div className={`relative ${className}`}>
97
+ <details className="group">
98
+ <summary className="flex items-center gap-2 px-3 py-2 border rounded-md cursor-pointer text-sm hover:bg-accent list-none">
99
+ <svg className="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
100
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M21 12a9 9 0 01-9 9m9-9a9 9 0 00-9-9m9 9H3m9 9a9 9 0 01-9-9m9 9c1.657 0 3-4.03 3-9s-1.343-9-3-9m0 18c-1.657 0-3-4.03-3-9s1.343-9 3-9m-9 9a9 9 0 019-9" />
101
+ </svg>
102
+ {showFlags && currentLang?.flagEmoji && <span>{currentLang.flagEmoji}</span>}
103
+ <span>{currentLang?.label || 'Select language'}</span>
104
+ {isLoading && <span className="animate-spin">⏳</span>}
105
+ </summary>
106
+ <div className="absolute top-full left-0 mt-1 w-48 bg-popover border rounded-md shadow-lg z-50">
107
+ {languages.map((lang) => (
108
+ <button
109
+ key={lang.code}
110
+ onClick={() => {
111
+ changeLanguage(lang.code)
112
+ // Close the details
113
+ const details = document.querySelector('details[open]')
114
+ if (details) details.removeAttribute('open')
115
+ }}
116
+ className="w-full px-3 py-2 text-left text-sm hover:bg-accent flex items-center gap-2"
117
+ >
118
+ {showFlags && lang.flagEmoji && <span>{lang.flagEmoji}</span>}
119
+ {lang.label}
120
+ </button>
121
+ ))}
122
+ </div>
123
+ </details>
124
+ </div>
125
+ )
126
+ }
127
+
128
+ const meta: Meta<typeof MockLanguageSelector> = {
129
+ title: 'Blocks/LanguageSelector',
130
+ component: MockLanguageSelector,
131
+ parameters: {
132
+ layout: 'centered',
133
+ docs: {
134
+ description: {
135
+ component:
136
+ 'A language selector component with dropdown or select variants, flag emojis, and loading states. Requires LanguageProvider context.',
137
+ },
138
+ },
139
+ },
140
+ tags: ['autodocs'],
141
+ argTypes: {
142
+ variant: {
143
+ control: 'select',
144
+ options: ['dropdown', 'select'],
145
+ },
146
+ showFlags: {
147
+ control: 'boolean',
148
+ },
149
+ },
150
+ }
151
+
152
+ export default meta
153
+ type Story = StoryObj<typeof MockLanguageSelector>
154
+
155
+ export const Default: Story = {
156
+ render: () => (
157
+ <MockLanguageProvider>
158
+ <MockLanguageSelector />
159
+ </MockLanguageProvider>
160
+ ),
161
+ }
162
+
163
+ export const SelectVariant: Story = {
164
+ render: () => (
165
+ <MockLanguageProvider>
166
+ <MockLanguageSelector variant="select" />
167
+ </MockLanguageProvider>
168
+ ),
169
+ }
170
+
171
+ export const WithoutFlags: Story = {
172
+ render: () => (
173
+ <MockLanguageProvider>
174
+ <div className="space-y-4">
175
+ <div>
176
+ <p className="text-sm text-muted-foreground mb-2">Dropdown without flags</p>
177
+ <MockLanguageSelector showFlags={false} />
178
+ </div>
179
+ <div>
180
+ <p className="text-sm text-muted-foreground mb-2">Select without flags</p>
181
+ <MockLanguageSelector variant="select" showFlags={false} />
182
+ </div>
183
+ </div>
184
+ </MockLanguageProvider>
185
+ ),
186
+ }
187
+
188
+ export const ManyLanguages: Story = {
189
+ render: () => (
190
+ <MockLanguageProvider languages={mockLanguages}>
191
+ <div className="space-y-4">
192
+ <div>
193
+ <p className="text-sm text-muted-foreground mb-2">Dropdown with many languages</p>
194
+ <MockLanguageSelector />
195
+ </div>
196
+ <div>
197
+ <p className="text-sm text-muted-foreground mb-2">Select with many languages</p>
198
+ <MockLanguageSelector variant="select" />
199
+ </div>
200
+ </div>
201
+ </MockLanguageProvider>
202
+ ),
203
+ }
204
+
205
+ export const TwoLanguages: Story = {
206
+ render: () => (
207
+ <MockLanguageProvider languages={mockLanguages.slice(0, 2)}>
208
+ <div className="space-y-4">
209
+ <div>
210
+ <p className="text-sm text-muted-foreground mb-2">Dropdown with 2 languages</p>
211
+ <MockLanguageSelector />
212
+ </div>
213
+ <div>
214
+ <p className="text-sm text-muted-foreground mb-2">Select with 2 languages</p>
215
+ <MockLanguageSelector variant="select" />
216
+ </div>
217
+ </div>
218
+ </MockLanguageProvider>
219
+ ),
220
+ }
221
+
222
+ export const InHeaderContext: Story = {
223
+ render: () => (
224
+ <MockLanguageProvider>
225
+ <div className="w-[800px] border rounded-lg">
226
+ <header className="flex items-center justify-between px-4 py-3 border-b">
227
+ <div className="flex items-center gap-4">
228
+ <span className="font-bold text-lg">MyApp</span>
229
+ <nav className="flex gap-4 text-sm">
230
+ <a href="#" className="text-muted-foreground hover:text-foreground">Home</a>
231
+ <a href="#" className="text-muted-foreground hover:text-foreground">Products</a>
232
+ <a href="#" className="text-muted-foreground hover:text-foreground">About</a>
233
+ </nav>
234
+ </div>
235
+ <div className="flex items-center gap-3">
236
+ <MockLanguageSelector />
237
+ <button className="px-3 py-1.5 text-sm bg-primary text-primary-foreground rounded-md">
238
+ Sign In
239
+ </button>
240
+ </div>
241
+ </header>
242
+ <main className="p-6">
243
+ <h1 className="text-2xl font-bold mb-2">Welcome</h1>
244
+ <p className="text-muted-foreground">
245
+ Select a language from the header to change the interface language.
246
+ </p>
247
+ </main>
248
+ </div>
249
+ </MockLanguageProvider>
250
+ ),
251
+ }
252
+
253
+ export const InFooterContext: Story = {
254
+ render: () => (
255
+ <MockLanguageProvider>
256
+ <div className="w-[800px] border rounded-lg">
257
+ <main className="p-6 min-h-[200px]">
258
+ <h1 className="text-2xl font-bold mb-2">Page Content</h1>
259
+ <p className="text-muted-foreground">
260
+ The language selector can also be placed in the footer.
261
+ </p>
262
+ </main>
263
+ <footer className="flex items-center justify-between px-4 py-3 border-t bg-muted/30">
264
+ <span className="text-sm text-muted-foreground">
265
+ © 2024 MyApp Inc.
266
+ </span>
267
+ <div className="flex items-center gap-4">
268
+ <a href="#" className="text-sm text-muted-foreground hover:text-foreground">Privacy</a>
269
+ <a href="#" className="text-sm text-muted-foreground hover:text-foreground">Terms</a>
270
+ <MockLanguageSelector variant="select" />
271
+ </div>
272
+ </footer>
273
+ </div>
274
+ </MockLanguageProvider>
275
+ ),
276
+ }
277
+
278
+ export const BothVariantsSideBySide: Story = {
279
+ render: () => (
280
+ <MockLanguageProvider>
281
+ <div className="flex gap-8 items-start">
282
+ <div>
283
+ <p className="text-sm font-medium mb-2">Dropdown Variant</p>
284
+ <MockLanguageSelector variant="dropdown" />
285
+ </div>
286
+ <div>
287
+ <p className="text-sm font-medium mb-2">Select Variant</p>
288
+ <MockLanguageSelector variant="select" />
289
+ </div>
290
+ </div>
291
+ </MockLanguageProvider>
292
+ ),
293
+ }
294
+
295
+ export const StartWithDifferentLanguage: Story = {
296
+ render: () => (
297
+ <div className="space-y-6">
298
+ <MockLanguageProvider initialLanguage="en">
299
+ <div>
300
+ <p className="text-sm text-muted-foreground mb-2">Starting with English</p>
301
+ <MockLanguageSelector />
302
+ </div>
303
+ </MockLanguageProvider>
304
+ <MockLanguageProvider initialLanguage="es" languages={mockLanguages.slice(0, 4)}>
305
+ <div>
306
+ <p className="text-sm text-muted-foreground mb-2">Starting with Spanish</p>
307
+ <MockLanguageSelector />
308
+ </div>
309
+ </MockLanguageProvider>
310
+ <MockLanguageProvider initialLanguage="de" languages={mockLanguages.slice(0, 4)}>
311
+ <div>
312
+ <p className="text-sm text-muted-foreground mb-2">Starting with German</p>
313
+ <MockLanguageSelector />
314
+ </div>
315
+ </MockLanguageProvider>
316
+ </div>
317
+ ),
318
+ }
319
+
320
+ export const AsianLanguages: Story = {
321
+ render: () => {
322
+ const asianLanguages: LanguageConfig[] = [
323
+ { code: 'ja', label: '日本語', flagEmoji: '🇯🇵' },
324
+ { code: 'zh', label: '中文', flagEmoji: '🇨🇳' },
325
+ { code: 'ko', label: '한국어', flagEmoji: '🇰🇷' },
326
+ { code: 'th', label: 'ไทย', flagEmoji: '🇹🇭' },
327
+ { code: 'vi', label: 'Tiếng Việt', flagEmoji: '🇻🇳' },
328
+ ]
329
+
330
+ return (
331
+ <MockLanguageProvider initialLanguage="ja" languages={asianLanguages}>
332
+ <div className="space-y-4">
333
+ <div>
334
+ <p className="text-sm text-muted-foreground mb-2">Dropdown with Asian languages</p>
335
+ <MockLanguageSelector />
336
+ </div>
337
+ <div>
338
+ <p className="text-sm text-muted-foreground mb-2">Select with Asian languages</p>
339
+ <MockLanguageSelector variant="select" />
340
+ </div>
341
+ </div>
342
+ </MockLanguageProvider>
343
+ )
344
+ },
345
+ }