@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.
@@ -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, CardFooter, CardHeader, CardTitle } from "../../components/card"
7
8
  import { Badge } from "../../components/badge"
@@ -82,6 +83,22 @@ export interface WakaPricingProps {
82
83
  showComparison?: boolean
83
84
  /** Classes CSS additionnelles */
84
85
  className?: string
86
+ /** Composant de lien personnalisé (ex: next/link) pour navigation SPA */
87
+ LinkComponent?: React.ComponentType<LinkComponentProps>
88
+ }
89
+
90
+ // ============================================
91
+ // CONTEXT
92
+ // ============================================
93
+
94
+ interface PricingContextValue {
95
+ LinkComponent?: React.ComponentType<LinkComponentProps>
96
+ }
97
+
98
+ const PricingContext = React.createContext<PricingContextValue>({})
99
+
100
+ function usePricingContext() {
101
+ return React.useContext(PricingContext)
85
102
  }
86
103
 
87
104
  // ============================================
@@ -135,6 +152,8 @@ interface PricingCardProps {
135
152
  }
136
153
 
137
154
  function PricingCard({ plan, billingPeriod }: PricingCardProps) {
155
+ const { LinkComponent } = usePricingContext()
156
+
138
157
  const price = billingPeriod === "yearly" && plan.priceYearly !== undefined
139
158
  ? plan.priceYearly
140
159
  : plan.price
@@ -236,7 +255,11 @@ function PricingCard({ plan, billingPeriod }: PricingCardProps) {
236
255
  asChild={!!plan.href}
237
256
  >
238
257
  {plan.href ? (
239
- <a href={plan.href}>{plan.buttonText || "Choisir"}</a>
258
+ LinkComponent ? (
259
+ <LinkComponent href={plan.href}>{plan.buttonText || "Choisir"}</LinkComponent>
260
+ ) : (
261
+ <a href={plan.href}>{plan.buttonText || "Choisir"}</a>
262
+ )
240
263
  ) : (
241
264
  plan.buttonText || "Choisir"
242
265
  )}
@@ -252,6 +275,8 @@ interface PricingTableProps {
252
275
  }
253
276
 
254
277
  function PricingTable({ plans, billingPeriod }: PricingTableProps) {
278
+ const { LinkComponent } = usePricingContext()
279
+
255
280
  // Get all unique features
256
281
  const allFeatures = Array.from(
257
282
  new Set(plans.flatMap((plan) => plan.features.map((f) => f.name)))
@@ -367,7 +392,11 @@ function PricingTable({ plans, billingPeriod }: PricingTableProps) {
367
392
  asChild={!!plan.href}
368
393
  >
369
394
  {plan.href ? (
370
- <a href={plan.href}>{plan.buttonText || "Choisir"}</a>
395
+ LinkComponent ? (
396
+ <LinkComponent href={plan.href}>{plan.buttonText || "Choisir"}</LinkComponent>
397
+ ) : (
398
+ <a href={plan.href}>{plan.buttonText || "Choisir"}</a>
399
+ )
371
400
  ) : (
372
401
  plan.buttonText || "Choisir"
373
402
  )}
@@ -420,6 +449,7 @@ export function WakaPricing({
420
449
  columns = 3,
421
450
  showComparison = false,
422
451
  className,
452
+ LinkComponent,
423
453
  }: WakaPricingProps) {
424
454
  const [internalBillingPeriod, setInternalBillingPeriod] = React.useState<"monthly" | "yearly">("monthly")
425
455
 
@@ -439,8 +469,11 @@ export function WakaPricing({
439
469
  4: "md:grid-cols-2 lg:grid-cols-4",
440
470
  }
441
471
 
472
+ const contextValue = React.useMemo(() => ({ LinkComponent }), [LinkComponent])
473
+
442
474
  return (
443
- <div className={cn("container py-16", className)}>
475
+ <PricingContext.Provider value={contextValue}>
476
+ <div className={cn("container py-16", className)}>
444
477
  {/* Header */}
445
478
  <div className="text-center mb-12">
446
479
  <h1 className="text-3xl md:text-4xl font-bold tracking-tight mb-4">
@@ -477,7 +510,8 @@ export function WakaPricing({
477
510
  {faq && faq.length > 0 && (
478
511
  <PricingFAQSection title={faqTitle} items={faq} />
479
512
  )}
480
- </div>
513
+ </div>
514
+ </PricingContext.Provider>
481
515
  )
482
516
  }
483
517
 
@@ -78,6 +78,10 @@ export interface SidebarLogoConfig {
78
78
  component?: React.ReactNode
79
79
  }
80
80
 
81
+ // Re-export du type partagé
82
+ export type { LinkComponentProps } from "../../types/link"
83
+ import type { LinkComponentProps } from "../../types/link"
84
+
81
85
  export interface WakaSidebarProps {
82
86
  /** Configuration du logo */
83
87
  logo?: SidebarLogoConfig
@@ -119,6 +123,8 @@ export interface WakaSidebarProps {
119
123
  header?: React.ReactNode
120
124
  /** Mode de positionnement : "fixed" pour app layout, "relative" pour preview/demo */
121
125
  position?: "fixed" | "relative"
126
+ /** Composant de lien personnalisé (ex: next/link) pour navigation SPA */
127
+ LinkComponent?: React.ComponentType<LinkComponentProps>
122
128
  }
123
129
 
124
130
  // ============ CONTEXT ============
@@ -131,6 +137,7 @@ interface SidebarContextValue {
131
137
  isMobile: boolean
132
138
  closeOnNavigate: boolean
133
139
  onClose: () => void
140
+ LinkComponent?: React.ComponentType<LinkComponentProps>
134
141
  }
135
142
 
136
143
  const SidebarContext = React.createContext<SidebarContextValue | null>(null)
@@ -149,6 +156,8 @@ function useSidebarContext() {
149
156
  * Logo de la sidebar
150
157
  */
151
158
  function SidebarLogo({ config }: { config: SidebarLogoConfig }) {
159
+ const { LinkComponent } = useSidebarContext()
160
+
152
161
  const content = config.component ? (
153
162
  config.component
154
163
  ) : (
@@ -167,6 +176,13 @@ function SidebarLogo({ config }: { config: SidebarLogoConfig }) {
167
176
  )
168
177
 
169
178
  if (config.href) {
179
+ if (LinkComponent) {
180
+ return (
181
+ <LinkComponent href={config.href} className="block" onClick={config.onClick}>
182
+ {content}
183
+ </LinkComponent>
184
+ )
185
+ }
170
186
  return (
171
187
  <a href={config.href} className="block" onClick={config.onClick}>
172
188
  {content}
@@ -197,7 +213,7 @@ function SidebarMenuItemComponent({
197
213
  isChild?: boolean
198
214
  renderItem?: (item: SidebarMenuItem, isChild: boolean) => React.ReactNode
199
215
  }) {
200
- const { activeId, setActiveId, expandedIds, toggleExpanded, isMobile, closeOnNavigate, onClose } =
216
+ const { activeId, setActiveId, expandedIds, toggleExpanded, isMobile, closeOnNavigate, onClose, LinkComponent } =
201
217
  useSidebarContext()
202
218
 
203
219
  const hasChildren = item.children && item.children.length > 0
@@ -284,21 +300,23 @@ function SidebarMenuItemComponent({
284
300
 
285
301
  // Lien ou bouton simple
286
302
  if (item.href) {
287
- return (
288
- <a
289
- href={item.href}
290
- onClick={(e) => {
291
- if (item.disabled) {
292
- e.preventDefault()
293
- return
294
- }
295
- handleClick()
296
- }}
297
- className="block"
298
- >
299
- {itemContent}
300
- </a>
301
- )
303
+ const linkProps = {
304
+ href: item.href,
305
+ className: "block",
306
+ onClick: (e: React.MouseEvent) => {
307
+ if (item.disabled) {
308
+ e.preventDefault()
309
+ return
310
+ }
311
+ handleClick()
312
+ },
313
+ }
314
+
315
+ if (LinkComponent) {
316
+ return <LinkComponent {...linkProps}>{itemContent}</LinkComponent>
317
+ }
318
+
319
+ return <a {...linkProps}>{itemContent}</a>
302
320
  }
303
321
 
304
322
  return (
@@ -317,6 +335,8 @@ function SidebarMenuItemComponent({
317
335
  * Menu utilisateur
318
336
  */
319
337
  function SidebarUser({ config }: { config: SidebarUserConfig }) {
338
+ const { LinkComponent } = useSidebarContext()
339
+
320
340
  const initials =
321
341
  config.initials ||
322
342
  config.name
@@ -366,10 +386,17 @@ function SidebarUser({ config }: { config: SidebarUserConfig }) {
366
386
  asChild={!!action.href}
367
387
  >
368
388
  {action.href ? (
369
- <a href={action.href} className="flex items-center gap-2">
370
- {action.icon}
371
- {action.label}
372
- </a>
389
+ LinkComponent ? (
390
+ <LinkComponent href={action.href} className="flex items-center gap-2">
391
+ {action.icon}
392
+ {action.label}
393
+ </LinkComponent>
394
+ ) : (
395
+ <a href={action.href} className="flex items-center gap-2">
396
+ {action.icon}
397
+ {action.label}
398
+ </a>
399
+ )
373
400
  ) : (
374
401
  <span className="flex items-center gap-2">
375
402
  {action.icon}
@@ -487,6 +514,7 @@ export function WakaSidebar({
487
514
  footer,
488
515
  header,
489
516
  position = "fixed",
517
+ LinkComponent,
490
518
  }: WakaSidebarProps) {
491
519
  // État interne pour mobile si non contrôlé
492
520
  const [internalOpen, setInternalOpen] = React.useState(false)
@@ -552,12 +580,13 @@ export function WakaSidebar({
552
580
  isMobile,
553
581
  closeOnNavigate: true,
554
582
  onClose: () => setIsOpen(false),
583
+ LinkComponent,
555
584
  }
556
585
 
557
586
  // Styles personnalisés - utilise les variables CSS du thème par défaut
558
587
  const customStyles = {
559
588
  "--sidebar-bg": backgroundColor || "hsl(var(--sidebar-background, var(--card)))",
560
- "--sidebar-text": textColor || "hsl(var(--sidebar-foreground, var(--card-foreground)))",
589
+ "--sidebar-text": textColor || "hsl(var(--sidebar-foreground, var(--muted-foreground)))",
561
590
  "--sidebar-active": activeColor || "hsl(var(--sidebar-primary, var(--primary)))",
562
591
  "--sidebar-active-foreground": "hsl(var(--sidebar-primary-foreground, var(--primary-foreground)))",
563
592
  "--sidebar-hover": hoverColor || "hsl(var(--sidebar-accent, var(--accent)))",
@@ -50,6 +50,15 @@
50
50
  --warning-foreground: 0 0% 0%;
51
51
  --info: 199 89% 48%;
52
52
  --info-foreground: 0 0% 100%;
53
+
54
+ /* Sidebar colors */
55
+ --sidebar-background: 222.2 84% 4.9%;
56
+ --sidebar-foreground: 210 40% 98%;
57
+ --sidebar-primary: 222.2 47.4% 11.2%;
58
+ --sidebar-primary-foreground: 210 40% 98%;
59
+ --sidebar-accent: 217.2 32.6% 17.5%;
60
+ --sidebar-accent-foreground: 210 40% 98%;
61
+ --sidebar-border: 217.2 32.6% 17.5%;
53
62
  }
54
63
 
55
64
  .dark,
@@ -86,6 +95,15 @@
86
95
  --warning-foreground: 0 0% 0%;
87
96
  --info: 199 89% 48%;
88
97
  --info-foreground: 0 0% 100%;
98
+
99
+ /* Sidebar colors - dark */
100
+ --sidebar-background: 222.2 84% 4.9%;
101
+ --sidebar-foreground: 210 40% 98%;
102
+ --sidebar-primary: 210 40% 98%;
103
+ --sidebar-primary-foreground: 222.2 47.4% 11.2%;
104
+ --sidebar-accent: 217.2 32.6% 17.5%;
105
+ --sidebar-accent-foreground: 210 40% 98%;
106
+ --sidebar-border: 217.2 32.6% 17.5%;
89
107
  }
90
108
 
91
109
  /* ============================================
@@ -33,6 +33,15 @@
33
33
  --warning-foreground: 0 0% 0%;
34
34
  --info: 199 89% 48%;
35
35
  --info-foreground: 0 0% 100%;
36
+
37
+ /* Sidebar colors */
38
+ --sidebar-background: 120 33% 9%;
39
+ --sidebar-foreground: 120 56% 69%;
40
+ --sidebar-primary: 120 45% 33%;
41
+ --sidebar-primary-foreground: 0 0% 100%;
42
+ --sidebar-accent: 120 36% 28%;
43
+ --sidebar-accent-foreground: 120 56% 69%;
44
+ --sidebar-border: 120 36% 28%;
36
45
  }
37
46
 
38
47
  [data-theme="forest"].dark {
@@ -68,4 +77,13 @@
68
77
  --warning-foreground: 0 0% 0%;
69
78
  --info: 199 89% 48%;
70
79
  --info-foreground: 0 0% 100%;
80
+
81
+ /* Sidebar colors - dark */
82
+ --sidebar-background: 120 33% 9%;
83
+ --sidebar-foreground: 120 56% 69%;
84
+ --sidebar-primary: 120 40% 56%;
85
+ --sidebar-primary-foreground: 120 33% 9%;
86
+ --sidebar-accent: 120 36% 28%;
87
+ --sidebar-accent-foreground: 120 56% 69%;
88
+ --sidebar-border: 120 36% 28%;
71
89
  }
@@ -33,6 +33,15 @@
33
33
  --warning-foreground: 0 0% 0%;
34
34
  --info: 199 89% 48%;
35
35
  --info-foreground: 0 0% 100%;
36
+
37
+ /* Sidebar colors */
38
+ --sidebar-background: 0 0% 4%;
39
+ --sidebar-foreground: 0 0% 98%;
40
+ --sidebar-primary: 0 0% 9%;
41
+ --sidebar-primary-foreground: 0 0% 98%;
42
+ --sidebar-accent: 0 0% 15%;
43
+ --sidebar-accent-foreground: 0 0% 98%;
44
+ --sidebar-border: 0 0% 15%;
36
45
  }
37
46
 
38
47
  [data-theme="monochrome"].dark {
@@ -68,4 +77,13 @@
68
77
  --warning-foreground: 0 0% 0%;
69
78
  --info: 199 89% 48%;
70
79
  --info-foreground: 0 0% 100%;
80
+
81
+ /* Sidebar colors - dark */
82
+ --sidebar-background: 0 0% 4%;
83
+ --sidebar-foreground: 0 0% 98%;
84
+ --sidebar-primary: 0 0% 98%;
85
+ --sidebar-primary-foreground: 0 0% 4%;
86
+ --sidebar-accent: 0 0% 15%;
87
+ --sidebar-accent-foreground: 0 0% 98%;
88
+ --sidebar-border: 0 0% 15%;
71
89
  }
@@ -33,6 +33,15 @@
33
33
  --warning-foreground: 0 0% 0%;
34
34
  --info: 199 89% 48%;
35
35
  --info-foreground: 0 0% 100%;
36
+
37
+ /* Sidebar colors */
38
+ --sidebar-background: 195 50% 8%;
39
+ --sidebar-foreground: 180 75% 60%;
40
+ --sidebar-primary: 184 93% 29%;
41
+ --sidebar-primary-foreground: 0 0% 100%;
42
+ --sidebar-accent: 189 59% 21%;
43
+ --sidebar-accent-foreground: 180 75% 60%;
44
+ --sidebar-border: 189 59% 21%;
36
45
  }
37
46
 
38
47
  [data-theme="perpetuity"].dark {
@@ -68,4 +77,13 @@
68
77
  --warning-foreground: 0 0% 0%;
69
78
  --info: 199 89% 48%;
70
79
  --info-foreground: 0 0% 100%;
80
+
81
+ /* Sidebar colors - dark */
82
+ --sidebar-background: 195 50% 8%;
83
+ --sidebar-foreground: 180 75% 60%;
84
+ --sidebar-primary: 180 75% 60%;
85
+ --sidebar-primary-foreground: 195 50% 8%;
86
+ --sidebar-accent: 189 59% 21%;
87
+ --sidebar-accent-foreground: 180 75% 60%;
88
+ --sidebar-border: 189 59% 21%;
71
89
  }
@@ -33,6 +33,15 @@
33
33
  --warning-foreground: 0 0% 0%;
34
34
  --info: 199 89% 48%;
35
35
  --info-foreground: 0 0% 100%;
36
+
37
+ /* Sidebar colors */
38
+ --sidebar-background: 18 56% 7%;
39
+ --sidebar-foreground: 28 92% 83%;
40
+ --sidebar-primary: 21 90% 48%;
41
+ --sidebar-primary-foreground: 0 0% 100%;
42
+ --sidebar-accent: 26 80% 31%;
43
+ --sidebar-accent-foreground: 28 92% 83%;
44
+ --sidebar-border: 28 75% 26%;
36
45
  }
37
46
 
38
47
  [data-theme="sunset"].dark {
@@ -68,4 +77,13 @@
68
77
  --warning-foreground: 0 0% 0%;
69
78
  --info: 199 89% 48%;
70
79
  --info-foreground: 0 0% 100%;
80
+
81
+ /* Sidebar colors - dark */
82
+ --sidebar-background: 18 56% 7%;
83
+ --sidebar-foreground: 28 92% 83%;
84
+ --sidebar-primary: 38 92% 50%;
85
+ --sidebar-primary-foreground: 18 56% 7%;
86
+ --sidebar-accent: 26 80% 31%;
87
+ --sidebar-accent-foreground: 28 92% 83%;
88
+ --sidebar-border: 28 75% 26%;
71
89
  }
@@ -33,6 +33,15 @@
33
33
  --warning-foreground: 0 0% 0%;
34
34
  --info: 199 89% 48%;
35
35
  --info-foreground: 0 0% 100%;
36
+
37
+ /* Sidebar colors */
38
+ --sidebar-background: 270 45% 11%;
39
+ --sidebar-foreground: 275 100% 92%;
40
+ --sidebar-primary: 262 83% 66%;
41
+ --sidebar-primary-foreground: 0 0% 100%;
42
+ --sidebar-accent: 268 35% 30%;
43
+ --sidebar-accent-foreground: 275 100% 92%;
44
+ --sidebar-border: 268 35% 30%;
36
45
  }
37
46
 
38
47
  [data-theme="twilight"].dark {
@@ -68,4 +77,13 @@
68
77
  --warning-foreground: 0 0% 0%;
69
78
  --info: 199 89% 48%;
70
79
  --info-foreground: 0 0% 100%;
80
+
81
+ /* Sidebar colors - dark */
82
+ --sidebar-background: 270 45% 11%;
83
+ --sidebar-foreground: 275 100% 92%;
84
+ --sidebar-primary: 262 91% 76%;
85
+ --sidebar-primary-foreground: 270 45% 11%;
86
+ --sidebar-accent: 268 35% 30%;
87
+ --sidebar-accent-foreground: 275 100% 92%;
88
+ --sidebar-border: 268 35% 30%;
71
89
  }