@wakastellar/ui 2.3.2 → 2.3.5

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.
@@ -0,0 +1 @@
1
+ export type { LinkComponentProps } from './link';
@@ -0,0 +1,23 @@
1
+ import * as React from "react";
2
+ /**
3
+ * Props du composant de lien personnalisé pour navigation SPA
4
+ * Permet d'injecter next/link ou tout autre composant de routing
5
+ *
6
+ * @example
7
+ * ```tsx
8
+ * import Link from 'next/link'
9
+ * import type { LinkComponentProps } from '@wakastellar/ui'
10
+ *
11
+ * const NextLink = ({ href, className, onClick, children }: LinkComponentProps) => (
12
+ * <Link href={href} className={className} onClick={onClick}>
13
+ * {children}
14
+ * </Link>
15
+ * )
16
+ * ```
17
+ */
18
+ export interface LinkComponentProps {
19
+ href: string;
20
+ className?: string;
21
+ onClick?: (e: React.MouseEvent) => void;
22
+ children: React.ReactNode;
23
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wakastellar/ui",
3
- "version": "2.3.2",
3
+ "version": "2.3.5",
4
4
  "description": "Zero-config UI Library for Next.js with TweakCN theming and i18n support",
5
5
  "keywords": [
6
6
  "ui",
@@ -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 { Card, CardContent, CardDescription, CardHeader, CardTitle } from "../../components/card"
6
7
  import { Button } from "../../components/button"
7
8
  import { Badge } from "../../components/badge"
@@ -119,6 +120,22 @@ export interface WakaDashboardProps {
119
120
  statsColumns?: 2 | 3 | 4
120
121
  /** Classes CSS additionnelles */
121
122
  className?: string
123
+ /** Composant de lien personnalisé (ex: next/link) pour navigation SPA */
124
+ LinkComponent?: React.ComponentType<LinkComponentProps>
125
+ }
126
+
127
+ // ============================================
128
+ // CONTEXT
129
+ // ============================================
130
+
131
+ interface DashboardContextValue {
132
+ LinkComponent?: React.ComponentType<LinkComponentProps>
133
+ }
134
+
135
+ const DashboardContext = React.createContext<DashboardContextValue>({})
136
+
137
+ function useDashboardContext() {
138
+ return React.useContext(DashboardContext)
122
139
  }
123
140
 
124
141
  // ============================================
@@ -196,27 +213,37 @@ function DashboardHeader({
196
213
  )}
197
214
 
198
215
  {/* Actions rapides */}
199
- {quickActions?.map((action) => (
200
- <Button
201
- key={action.id}
202
- variant={action.variant || "default"}
203
- size="sm"
204
- onClick={action.onClick}
205
- asChild={!!action.href}
206
- >
207
- {action.href ? (
208
- <a href={action.href}>
209
- {action.icon && <span className="mr-2">{action.icon}</span>}
210
- {action.label}
211
- </a>
212
- ) : (
213
- <>
214
- {action.icon && <span className="mr-2">{action.icon}</span>}
215
- {action.label}
216
- </>
217
- )}
218
- </Button>
219
- ))}
216
+ {quickActions?.map((action) => {
217
+ const { LinkComponent } = useDashboardContext()
218
+ return (
219
+ <Button
220
+ key={action.id}
221
+ variant={action.variant || "default"}
222
+ size="sm"
223
+ onClick={action.onClick}
224
+ asChild={!!action.href}
225
+ >
226
+ {action.href ? (
227
+ LinkComponent ? (
228
+ <LinkComponent href={action.href}>
229
+ {action.icon && <span className="mr-2">{action.icon}</span>}
230
+ {action.label}
231
+ </LinkComponent>
232
+ ) : (
233
+ <a href={action.href}>
234
+ {action.icon && <span className="mr-2">{action.icon}</span>}
235
+ {action.label}
236
+ </a>
237
+ )
238
+ ) : (
239
+ <>
240
+ {action.icon && <span className="mr-2">{action.icon}</span>}
241
+ {action.label}
242
+ </>
243
+ )}
244
+ </Button>
245
+ )
246
+ })}
220
247
  </div>
221
248
  </div>
222
249
  )
@@ -296,6 +323,8 @@ interface DashboardTableCardProps {
296
323
  }
297
324
 
298
325
  function DashboardTableCard({ config }: DashboardTableCardProps) {
326
+ const { LinkComponent } = useDashboardContext()
327
+
299
328
  return (
300
329
  <Card>
301
330
  <CardHeader className="flex flex-row items-center justify-between pb-2">
@@ -316,10 +345,17 @@ function DashboardTableCard({ config }: DashboardTableCardProps) {
316
345
  asChild={!!action.href}
317
346
  >
318
347
  {action.href ? (
319
- <a href={action.href}>
320
- {action.label}
321
- <ArrowUpRight className="ml-1 h-3 w-3" />
322
- </a>
348
+ LinkComponent ? (
349
+ <LinkComponent href={action.href}>
350
+ {action.label}
351
+ <ArrowUpRight className="ml-1 h-3 w-3" />
352
+ </LinkComponent>
353
+ ) : (
354
+ <a href={action.href}>
355
+ {action.label}
356
+ <ArrowUpRight className="ml-1 h-3 w-3" />
357
+ </a>
358
+ )
323
359
  ) : (
324
360
  action.label
325
361
  )}
@@ -415,9 +451,13 @@ export function WakaDashboard({
415
451
  statsLayout = "grid",
416
452
  statsColumns = 4,
417
453
  className,
454
+ LinkComponent,
418
455
  }: WakaDashboardProps) {
456
+ const contextValue = React.useMemo(() => ({ LinkComponent }), [LinkComponent])
457
+
419
458
  return (
420
- <div className={cn("space-y-6", className)}>
459
+ <DashboardContext.Provider value={contextValue}>
460
+ <div className={cn("space-y-6", className)}>
421
461
  {/* Header */}
422
462
  {showHeader && (
423
463
  <DashboardHeader
@@ -469,7 +509,8 @@ export function WakaDashboard({
469
509
  </div>
470
510
  )}
471
511
  </div>
472
- </div>
512
+ </div>
513
+ </DashboardContext.Provider>
473
514
  )
474
515
  }
475
516
 
@@ -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 {
7
8
  FileQuestion,
@@ -78,6 +79,8 @@ export interface WakaEmptyStateProps {
78
79
  children?: React.ReactNode
79
80
  /** Classes CSS additionnelles */
80
81
  className?: string
82
+ /** Composant de lien personnalisé (ex: next/link) pour navigation SPA */
83
+ LinkComponent?: React.ComponentType<LinkComponentProps>
81
84
  }
82
85
 
83
86
  // ============================================
@@ -238,6 +241,7 @@ export function WakaEmptyState({
238
241
  centered = true,
239
242
  children,
240
243
  className,
244
+ LinkComponent,
241
245
  }: WakaEmptyStateProps) {
242
246
  const config = emptyStateConfigs[type]
243
247
  const displayTitle = title ?? config.title
@@ -325,10 +329,17 @@ export function WakaEmptyState({
325
329
  asChild={!!action.href}
326
330
  >
327
331
  {action.href ? (
328
- <a href={action.href}>
329
- {action.icon && <span className="mr-2">{action.icon}</span>}
330
- {action.label}
331
- </a>
332
+ LinkComponent ? (
333
+ <LinkComponent href={action.href}>
334
+ {action.icon && <span className="mr-2">{action.icon}</span>}
335
+ {action.label}
336
+ </LinkComponent>
337
+ ) : (
338
+ <a href={action.href}>
339
+ {action.icon && <span className="mr-2">{action.icon}</span>}
340
+ {action.label}
341
+ </a>
342
+ )
332
343
  ) : (
333
344
  <>
334
345
  {action.icon && <span className="mr-2">{action.icon}</span>}
@@ -399,10 +410,17 @@ export function WakaEmptyState({
399
410
  asChild={!!action.href}
400
411
  >
401
412
  {action.href ? (
402
- <a href={action.href}>
403
- {action.icon && <span className="mr-2">{action.icon}</span>}
404
- {action.label}
405
- </a>
413
+ LinkComponent ? (
414
+ <LinkComponent href={action.href}>
415
+ {action.icon && <span className="mr-2">{action.icon}</span>}
416
+ {action.label}
417
+ </LinkComponent>
418
+ ) : (
419
+ <a href={action.href}>
420
+ {action.icon && <span className="mr-2">{action.icon}</span>}
421
+ {action.label}
422
+ </a>
423
+ )
406
424
  ) : (
407
425
  <>
408
426
  {action.icon && <span className="mr-2">{action.icon}</span>}
@@ -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 {
7
8
  Home,
@@ -65,6 +66,8 @@ export interface WakaErrorPageProps {
65
66
  size?: "sm" | "md" | "lg"
66
67
  /** Classes CSS additionnelles */
67
68
  className?: string
69
+ /** Composant de lien personnalisé (ex: next/link) pour navigation SPA */
70
+ LinkComponent?: React.ComponentType<LinkComponentProps>
68
71
  }
69
72
 
70
73
  // ============================================
@@ -186,6 +189,7 @@ export function WakaErrorPage({
186
189
  layout = "centered",
187
190
  size = "md",
188
191
  className,
192
+ LinkComponent,
189
193
  }: WakaErrorPageProps) {
190
194
  const config = errorConfigs[type]
191
195
  const displayCode = code ?? config.code
@@ -297,10 +301,17 @@ export function WakaErrorPage({
297
301
  asChild={!!action.href}
298
302
  >
299
303
  {action.href ? (
300
- <a href={action.href}>
301
- {action.icon && <span className="mr-2">{action.icon}</span>}
302
- {action.label}
303
- </a>
304
+ LinkComponent ? (
305
+ <LinkComponent href={action.href}>
306
+ {action.icon && <span className="mr-2">{action.icon}</span>}
307
+ {action.label}
308
+ </LinkComponent>
309
+ ) : (
310
+ <a href={action.href}>
311
+ {action.icon && <span className="mr-2">{action.icon}</span>}
312
+ {action.label}
313
+ </a>
314
+ )
304
315
  ) : (
305
316
  <>
306
317
  {action.icon && <span className="mr-2">{action.icon}</span>}
@@ -359,10 +370,17 @@ export function WakaErrorPage({
359
370
  asChild={!!action.href}
360
371
  >
361
372
  {action.href ? (
362
- <a href={action.href}>
363
- {action.icon && <span className="mr-2">{action.icon}</span>}
364
- {action.label}
365
- </a>
373
+ LinkComponent ? (
374
+ <LinkComponent href={action.href}>
375
+ {action.icon && <span className="mr-2">{action.icon}</span>}
376
+ {action.label}
377
+ </LinkComponent>
378
+ ) : (
379
+ <a href={action.href}>
380
+ {action.icon && <span className="mr-2">{action.icon}</span>}
381
+ {action.label}
382
+ </a>
383
+ )
366
384
  ) : (
367
385
  <>
368
386
  {action.icon && <span className="mr-2">{action.icon}</span>}
@@ -23,7 +23,7 @@ export type { LoginProps, LoginConfig, LoginColorConfig, LoginThemeConfig, Login
23
23
 
24
24
  // Sidebar
25
25
  export { WakaSidebar, useSidebar, SidebarLayout } from './sidebar'
26
- export type { WakaSidebarProps, SidebarMenuItem, SidebarUserConfig, SidebarLogoConfig, SidebarLayoutProps } from './sidebar'
26
+ export type { WakaSidebarProps, SidebarMenuItem, SidebarUserConfig, SidebarLogoConfig, SidebarLayoutProps, LinkComponentProps } from './sidebar'
27
27
 
28
28
  // Header
29
29
  export { WakaHeader } from './header'
@@ -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