@ttoss/react-billing 0.5.12 → 0.5.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs ADDED
@@ -0,0 +1,801 @@
1
+ /** Powered by @ttoss/config. https://ttoss.dev/docs/modules/packages/config/ */
2
+ import { Box, Button, Card, Flex, Heading, SegmentedControl, Spinner, Stack, Text, keyframes } from "@ttoss/ui";
3
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
4
+ import { Icon } from "@ttoss/react-icons";
5
+ import * as React from "react";
6
+ import { EnhancedTitle } from "@ttoss/components/EnhancedTitle";
7
+ import { MetricCard } from "@ttoss/components/MetricCard";
8
+
9
+ //#region src/components/planCard/PlanCardCtaSlot.tsx
10
+ const PlanCardCtaSlot = ({
11
+ buttonProps
12
+ }) => {
13
+ const {
14
+ label: ctaLabel = "Assine agora",
15
+ sx: buttonSx,
16
+ leftIcon,
17
+ variant: buttonVariant,
18
+ ...restButtonProps
19
+ } = buttonProps ?? {};
20
+ return /* @__PURE__ */jsx(Flex, {
21
+ sx: {
22
+ marginTop: "auto",
23
+ paddingY: "2",
24
+ paddingX: "6",
25
+ width: "full",
26
+ justifyContent: "center"
27
+ },
28
+ children: /* @__PURE__ */jsx(Button, {
29
+ ...restButtonProps,
30
+ variant: buttonVariant ?? "accent",
31
+ leftIcon,
32
+ sx: {
33
+ width: "full",
34
+ justifyContent: "center",
35
+ fontWeight: "semibold",
36
+ ...buttonSx
37
+ },
38
+ children: ctaLabel
39
+ })
40
+ });
41
+ };
42
+
43
+ //#endregion
44
+ //#region src/components/planCard/PlanCardVariants.ts
45
+ const normalizeVariant = variantType => {
46
+ if (variantType === "default") return "primary";
47
+ if (variantType === "enterprise") return "secondary";
48
+ return variantType ?? "primary";
49
+ };
50
+ /**
51
+ * PlanCard variants follow the same pattern as `Tooltip` variants:
52
+ * a simple mapping from `variantType` to a small set of style tokens.
53
+ */
54
+ const getPlanCardVariantStyles = variantType => {
55
+ const variant = normalizeVariant(variantType);
56
+ const variants = {
57
+ primary: {
58
+ backgroundColor: "display.background.primary.default",
59
+ color: "display.text.primary.default",
60
+ secondaryColor: "display.text.secondary.default",
61
+ borderColor: "display.border.muted.default",
62
+ positiveColor: "feedback.text.positive.default"
63
+ },
64
+ secondary: {
65
+ backgroundColor: "display.background.primary.active",
66
+ color: "action.text.primary.default",
67
+ secondaryColor: "action.text.primary.default",
68
+ borderColor: "display.border.muted.default",
69
+ positiveColor: "action.text.primary.default"
70
+ }
71
+ };
72
+ return variants[variant] ?? variants.primary;
73
+ };
74
+
75
+ //#endregion
76
+ //#region src/components/planCard/PlanCardFeaturesSlot.tsx
77
+ const featuresTitle = "RECURSOS";
78
+ const PlanCardFeaturesSlot = ({
79
+ features,
80
+ variant = "primary"
81
+ }) => {
82
+ const variantStyles = getPlanCardVariantStyles(variant);
83
+ const featurePositiveColor = variantStyles.positiveColor;
84
+ return /* @__PURE__ */jsx(Box, {
85
+ sx: {
86
+ paddingY: "4",
87
+ paddingX: "6",
88
+ width: "full",
89
+ borderTop: "md",
90
+ borderBottom: "md",
91
+ borderColor: "display.border.muted.default"
92
+ },
93
+ children: /* @__PURE__ */jsxs(Stack, {
94
+ sx: {
95
+ gap: "5",
96
+ paddingY: "3"
97
+ },
98
+ children: [/* @__PURE__ */jsx(Flex, {
99
+ sx: {
100
+ letterSpacing: "widest",
101
+ color: variantStyles.color
102
+ },
103
+ children: featuresTitle
104
+ }), /* @__PURE__ */jsx(Flex, {
105
+ sx: {
106
+ flexDirection: "column",
107
+ gap: "3"
108
+ },
109
+ children: features.map((feature, index) => {
110
+ if (React.isValidElement(feature)) return /* @__PURE__ */jsx(React.Fragment, {
111
+ children: feature
112
+ }, index);
113
+ return /* @__PURE__ */jsxs(Flex, {
114
+ sx: {
115
+ fontSize: "sm",
116
+ color: featurePositiveColor,
117
+ alignItems: "center",
118
+ gap: "3"
119
+ },
120
+ children: [/* @__PURE__ */jsx(Icon, {
121
+ icon: "fluent:checkmark-24-filled"
122
+ }), /* @__PURE__ */jsx(Text, {
123
+ sx: {
124
+ fontSize: "sm",
125
+ color: featurePositiveColor,
126
+ alignItems: "center"
127
+ },
128
+ children: String(feature)
129
+ })]
130
+ }, index);
131
+ })
132
+ })]
133
+ })
134
+ });
135
+ };
136
+
137
+ //#endregion
138
+ //#region src/components/planCard/PlanCardHeaderSlot.tsx
139
+ const PlanCardHeaderSlot = ({
140
+ title,
141
+ subtitle,
142
+ hasTopTag,
143
+ variant = "primary"
144
+ }) => {
145
+ const variantStyles = getPlanCardVariantStyles(variant);
146
+ return /* @__PURE__ */jsx(Box, {
147
+ sx: {
148
+ paddingY: hasTopTag ? "4" : "8",
149
+ paddingX: "8",
150
+ width: "full"
151
+ },
152
+ children: /* @__PURE__ */jsxs(Flex, {
153
+ sx: {
154
+ flexDirection: "column",
155
+ gap: "2",
156
+ width: "full",
157
+ alignItems: "center"
158
+ },
159
+ children: [/* @__PURE__ */jsx(Heading, {
160
+ sx: {
161
+ fontSize: "3xl",
162
+ color: variantStyles.color
163
+ },
164
+ children: title
165
+ }), subtitle && /* @__PURE__ */jsx(Text, {
166
+ sx: {
167
+ fontSize: "sm",
168
+ color: variantStyles.secondaryColor
169
+ },
170
+ children: subtitle
171
+ })]
172
+ })
173
+ });
174
+ };
175
+
176
+ //#endregion
177
+ //#region src/components/planCard/PlanCardMetadataSlot.tsx
178
+ const PlanCardMetadataSlot = ({
179
+ metadata,
180
+ variant = "primary"
181
+ }) => {
182
+ const variantStyles = getPlanCardVariantStyles(variant);
183
+ return /* @__PURE__ */jsx(Stack, {
184
+ sx: {
185
+ width: "full",
186
+ gap: "6"
187
+ },
188
+ children: metadata.map(service => {
189
+ const serviceKey = service.id ?? service.key ?? service.label;
190
+ return /* @__PURE__ */jsxs(Flex, {
191
+ sx: {
192
+ gap: "3",
193
+ flexDirection: "column",
194
+ width: "full"
195
+ },
196
+ children: [/* @__PURE__ */jsxs(Flex, {
197
+ sx: {
198
+ gap: "4",
199
+ alignItems: "center",
200
+ color: variantStyles.secondaryColor,
201
+ width: "full"
202
+ },
203
+ children: [service.icon && /* @__PURE__ */jsx(Icon, {
204
+ icon: service.icon
205
+ }), /* @__PURE__ */jsx(Text, {
206
+ sx: {
207
+ color: variantStyles.secondaryColor
208
+ },
209
+ children: service.label + String.fromCharCode(58)
210
+ })]
211
+ }), service.parameters.map(parameter => {
212
+ return /* @__PURE__ */jsx(Flex, {
213
+ sx: {
214
+ paddingX: "6",
215
+ paddingY: "4",
216
+ backgroundColor: "display.background.muted.default",
217
+ borderRadius: "lg",
218
+ width: "full"
219
+ },
220
+ children: /* @__PURE__ */jsxs(Flex, {
221
+ sx: {
222
+ width: "full",
223
+ display: "flex",
224
+ alignItems: "center",
225
+ justifyContent: "space-between",
226
+ gap: "2"
227
+ },
228
+ children: [/* @__PURE__ */jsx(Text, {
229
+ sx: {
230
+ fontSize: "sm",
231
+ textAlign: "center"
232
+ },
233
+ children: parameter.name
234
+ }), /* @__PURE__ */jsx(Text, {
235
+ sx: {
236
+ fontWeight: "semibold",
237
+ fontSize: "lg"
238
+ },
239
+ children: parameter.value
240
+ })]
241
+ })
242
+ }, parameter.name);
243
+ })]
244
+ }, serviceKey);
245
+ })
246
+ });
247
+ };
248
+
249
+ //#endregion
250
+ //#region src/components/planCard/PlanCardPriceSlot.tsx
251
+ const PlanCardPriceSlot = ({
252
+ price,
253
+ variant = "primary"
254
+ }) => {
255
+ const variantStyles = getPlanCardVariantStyles(variant);
256
+ return /* @__PURE__ */jsxs(Flex, {
257
+ sx: {
258
+ paddingY: "6",
259
+ paddingX: "8",
260
+ width: "full",
261
+ alignItems: "center",
262
+ flexDirection: "column",
263
+ gap: "3"
264
+ },
265
+ children: [/* @__PURE__ */jsxs(Flex, {
266
+ sx: {
267
+ alignItems: "baseline",
268
+ gap: "4",
269
+ justifyContent: "center"
270
+ },
271
+ children: [/* @__PURE__ */jsx(Text, {
272
+ sx: {
273
+ fontSize: "4xl",
274
+ fontWeight: "bold",
275
+ color: variantStyles.color
276
+ },
277
+ children: price.value
278
+ }), /* @__PURE__ */jsx(Text, {
279
+ sx: {
280
+ fontSize: "sm",
281
+ color: variantStyles.secondaryColor
282
+ },
283
+ children: price.interval
284
+ })]
285
+ }), price.description && /* @__PURE__ */jsx(Text, {
286
+ sx: {
287
+ fontSize: "md",
288
+ color: variantStyles.secondaryColor
289
+ },
290
+ children: price.description
291
+ })]
292
+ });
293
+ };
294
+
295
+ //#endregion
296
+ //#region src/components/planCard/PlanCardTopTagSlot.tsx
297
+ const PlanCardTopTagSlot = ({
298
+ children
299
+ }) => {
300
+ return /* @__PURE__ */jsx(Box, {
301
+ sx: {
302
+ padding: "4"
303
+ },
304
+ children
305
+ });
306
+ };
307
+
308
+ //#endregion
309
+ //#region src/components/planCard/PlanCard.tsx
310
+ const PlanCard = props => {
311
+ const {
312
+ variant = "primary",
313
+ topTag,
314
+ title,
315
+ subtitle,
316
+ metadata = [],
317
+ metadataVariant,
318
+ price,
319
+ features = [],
320
+ buttonProps,
321
+ ...cardProps
322
+ } = props;
323
+ const effectiveMetadataVariant = metadataVariant ?? variant;
324
+ const variantStyles = getPlanCardVariantStyles(variant);
325
+ return /* @__PURE__ */jsxs(Card, {
326
+ ...cardProps,
327
+ sx: {
328
+ display: "flex",
329
+ flexDirection: "column",
330
+ width: "full",
331
+ maxWidth: "410px",
332
+ backgroundColor: variantStyles.backgroundColor,
333
+ borderColor: variantStyles.borderColor,
334
+ ...cardProps.sx
335
+ },
336
+ children: [topTag && /* @__PURE__ */jsx(PlanCardTopTagSlot, {
337
+ variant,
338
+ children: topTag
339
+ }), /* @__PURE__ */jsx(PlanCardHeaderSlot, {
340
+ title,
341
+ subtitle,
342
+ hasTopTag: Boolean(topTag),
343
+ variant
344
+ }), metadata.length > 0 && /* @__PURE__ */jsx(Box, {
345
+ sx: {
346
+ paddingY: "4",
347
+ paddingX: "8",
348
+ width: "full"
349
+ },
350
+ children: /* @__PURE__ */jsx(PlanCardMetadataSlot, {
351
+ metadata,
352
+ variant: effectiveMetadataVariant
353
+ })
354
+ }), /* @__PURE__ */jsx(PlanCardPriceSlot, {
355
+ price,
356
+ variant
357
+ }), features.length > 0 && /* @__PURE__ */jsx(PlanCardFeaturesSlot, {
358
+ features,
359
+ variant
360
+ }), /* @__PURE__ */jsx(PlanCardCtaSlot, {
361
+ buttonProps,
362
+ variant
363
+ })]
364
+ });
365
+ };
366
+
367
+ //#endregion
368
+ //#region src/components/plansPanel/PlansPanel.tsx
369
+ /**
370
+ * PlansPanel renders a grid of PlanCard components with optional multi-dimensional
371
+ * SegmentedControl filters above them.
372
+ *
373
+ * Each filter dimension independently narrows the visible set of plan cards.
374
+ * The component supports both uncontrolled mode (internal state from `defaultValue`)
375
+ * and controlled mode (via `filterValues` + `onFilterChange`).
376
+ *
377
+ * @example
378
+ * ```tsx
379
+ * // Uncontrolled — component manages filter state internally
380
+ * <PlansPanel
381
+ * filters={[
382
+ * {
383
+ * id: 'interval',
384
+ * label: 'Billing Frequency',
385
+ * options: [
386
+ * { label: 'Monthly', value: 'monthly' },
387
+ * { label: 'Yearly', value: 'yearly' },
388
+ * ],
389
+ * defaultValue: 'monthly',
390
+ * },
391
+ * ]}
392
+ * plans={plans}
393
+ * activePlanId="plan_basic_monthly"
394
+ * onSelectPlan={(planId) => console.log(planId)}
395
+ * />
396
+ * ```
397
+ *
398
+ * @example
399
+ * ```tsx
400
+ * // Controlled — consumer owns filter state
401
+ * <PlansPanel
402
+ * filters={filters}
403
+ * filterValues={{ interval: 'yearly' }}
404
+ * onFilterChange={(id, value) =>
405
+ * setFilterValues((prev) => ({ ...prev, [id]: value }))
406
+ * }
407
+ * plans={plans}
408
+ * onSelectPlan={(planId) => checkout(planId)}
409
+ * />
410
+ * ```
411
+ */
412
+ const PlansPanel = ({
413
+ filters = [],
414
+ plans,
415
+ activePlanId,
416
+ onSelectPlan,
417
+ filterValues: controlledFilterValues,
418
+ onFilterChange
419
+ }) => {
420
+ const [internalFilterValues, setInternalFilterValues] = React.useState(() => {
421
+ const initial = {};
422
+ for (const filter of filters) if (filter.defaultValue !== void 0) initial[filter.id] = filter.defaultValue;
423
+ return initial;
424
+ });
425
+ const isControlled = controlledFilterValues !== void 0;
426
+ const activeFilterValues = isControlled ? controlledFilterValues : internalFilterValues;
427
+ const handleFilterChange = (id, value) => {
428
+ const strValue = String(value);
429
+ if (isControlled) onFilterChange?.(id, strValue);else setInternalFilterValues(prev => {
430
+ return {
431
+ ...prev,
432
+ [id]: strValue
433
+ };
434
+ });
435
+ };
436
+ const visiblePlans = plans.filter(plan => {
437
+ if (!plan.filterValues || Object.keys(plan.filterValues).length === 0) return true;
438
+ return Object.entries(activeFilterValues).every(([filterId, filterValue]) => {
439
+ if (!plan.filterValues || !(filterId in plan.filterValues)) return true;
440
+ return plan.filterValues[filterId] === filterValue;
441
+ });
442
+ });
443
+ return /* @__PURE__ */jsxs(Stack, {
444
+ sx: {
445
+ gap: "6",
446
+ width: "full",
447
+ alignItems: "center"
448
+ },
449
+ children: [filters.length > 0 && /* @__PURE__ */jsx(Stack, {
450
+ sx: {
451
+ gap: "4",
452
+ alignItems: "center"
453
+ },
454
+ children: filters.map(filter => {
455
+ return /* @__PURE__ */jsxs(Stack, {
456
+ sx: {
457
+ gap: "2",
458
+ alignItems: "center"
459
+ },
460
+ children: [filter.label && /* @__PURE__ */jsx(Text, {
461
+ sx: {
462
+ fontWeight: "semibold",
463
+ color: "display.text.primary.default"
464
+ },
465
+ children: filter.label
466
+ }), /* @__PURE__ */jsx(SegmentedControl, {
467
+ options: filter.options,
468
+ value: isControlled ? controlledFilterValues[filter.id] ?? filter.defaultValue : activeFilterValues[filter.id],
469
+ defaultValue: isControlled ? void 0 : filter.defaultValue,
470
+ onChange: value => {
471
+ return handleFilterChange(filter.id, value);
472
+ }
473
+ })]
474
+ }, filter.id);
475
+ })
476
+ }), /* @__PURE__ */jsx(Flex, {
477
+ sx: {
478
+ flexWrap: "wrap",
479
+ gap: "4",
480
+ width: "full",
481
+ justifyContent: "center"
482
+ },
483
+ children: visiblePlans.map(plan => {
484
+ const effectiveVariant = plan.id === activePlanId ? "secondary" : plan.variant ?? "primary";
485
+ return /* @__PURE__ */jsx(PlanCard, {
486
+ title: plan.title,
487
+ subtitle: plan.subtitle,
488
+ price: plan.price,
489
+ features: plan.features,
490
+ metadata: plan.metadata,
491
+ variant: effectiveVariant,
492
+ topTag: plan.topTag,
493
+ buttonProps: {
494
+ ...plan.buttonProps,
495
+ onClick: e => {
496
+ onSelectPlan?.(plan.id);
497
+ plan.buttonProps?.onClick?.(e);
498
+ }
499
+ }
500
+ }, plan.id);
501
+ })
502
+ })]
503
+ });
504
+ };
505
+
506
+ //#endregion
507
+ //#region src/components/subscriptionPanel/SubscriptionPanel.styles.ts
508
+ const gradientFlow = keyframes({
509
+ "0%": {
510
+ backgroundPosition: "0% 50%"
511
+ },
512
+ "50%": {
513
+ backgroundPosition: "100% 50%"
514
+ },
515
+ "100%": {
516
+ backgroundPosition: "0% 50%"
517
+ }
518
+ });
519
+ const getAccentGradientBackground = t => {
520
+ const theme = t;
521
+ const start = theme.colors?.action?.background?.accent?.default || theme.colors?.input?.background?.accent?.default;
522
+ if (!start) return void 0;
523
+ return `linear-gradient(270deg, ${start}, ${theme.colors?.action?.background?.accent?.active || theme.colors?.input?.background?.accent?.active || start}, ${start})`;
524
+ };
525
+ const getPrimaryGradientBackground = t => {
526
+ const theme = t;
527
+ const start = theme.colors?.action?.background?.primary?.default;
528
+ if (!start) return void 0;
529
+ return `linear-gradient(270deg, ${start}, ${theme.colors?.action?.background?.secondary?.default || start}, ${start})`;
530
+ };
531
+ const getVariantStyles = variantType => {
532
+ const variants = {
533
+ "spotlight-accent": {
534
+ backgroundColor: "input.background.accent.default",
535
+ color: "action.text.accent.default",
536
+ borderColor: "display.border.muted.default",
537
+ gradientBackground: getAccentGradientBackground
538
+ },
539
+ "spotlight-primary": {
540
+ backgroundColor: "action.background.primary.default",
541
+ color: "display.text.accent.default",
542
+ borderColor: "display.border.muted.default",
543
+ gradientBackground: getPrimaryGradientBackground
544
+ },
545
+ primary: {
546
+ backgroundColor: "action.background.primary.default",
547
+ color: "action.text.primary.default",
548
+ borderColor: "display.border.muted.default",
549
+ gradientBackground: void 0
550
+ },
551
+ secondary: {
552
+ backgroundColor: "action.background.secondary.default",
553
+ color: "action.text.primary.default",
554
+ borderColor: "display.border.muted.default",
555
+ gradientBackground: void 0
556
+ },
557
+ accent: {
558
+ backgroundColor: "action.background.accent.default",
559
+ color: "action.text.accent.default",
560
+ borderColor: "display.border.muted.default",
561
+ gradientBackground: void 0
562
+ }
563
+ };
564
+ return variants[variantType] || variants["spotlight-accent"];
565
+ };
566
+ const getSubscriptionPanelAccentBarSx = (variant = "spotlight-accent") => {
567
+ const variantStyles = getVariantStyles(variant);
568
+ const isSpotlight = variant.startsWith("spotlight-");
569
+ const gradientBg = variant === "spotlight-accent" ? getAccentGradientBackground : getPrimaryGradientBackground;
570
+ return {
571
+ height: "12px",
572
+ width: "full",
573
+ borderTopLeftRadius: "lg",
574
+ borderTopRightRadius: "lg",
575
+ backgroundColor: variantStyles.backgroundColor,
576
+ color: variantStyles.color,
577
+ borderColor: variantStyles.borderColor,
578
+ ...(isSpotlight ? {
579
+ background: gradientBg,
580
+ backgroundSize: "400% 400%",
581
+ animation: `${gradientFlow} 6s ease infinite`
582
+ } : {})
583
+ };
584
+ };
585
+ const SubscriptionPanelAccentBarSx = getSubscriptionPanelAccentBarSx("spotlight-accent");
586
+
587
+ //#endregion
588
+ //#region src/components/subscriptionPanel/SubscriptionPanelActionsSlot.tsx
589
+ /**
590
+ * Renders action buttons for the subscription card.
591
+ */
592
+ const SubscriptionPanelActionsSlot = ({
593
+ actions
594
+ }) => {
595
+ if (actions.length === 0) return null;
596
+ return /* @__PURE__ */jsx(Flex, {
597
+ sx: {
598
+ gap: "3",
599
+ flexDirection: ["column", "row"],
600
+ flexShrink: 0,
601
+ paddingX: "6",
602
+ paddingBottom: "6"
603
+ },
604
+ children: actions.map((action, index) => {
605
+ const {
606
+ label,
607
+ onClick,
608
+ leftIcon,
609
+ isLoading,
610
+ variant = "secondary",
611
+ disabled,
612
+ ...buttonProps
613
+ } = action;
614
+ return /* @__PURE__ */jsx(Button, {
615
+ onClick,
616
+ variant,
617
+ disabled: disabled || isLoading,
618
+ leftIcon: isLoading ? void 0 : leftIcon,
619
+ sx: {
620
+ gap: "2",
621
+ justifyContent: "center",
622
+ minWidth: "fit-content"
623
+ },
624
+ ...buttonProps,
625
+ children: isLoading ? /* @__PURE__ */jsxs(Fragment, {
626
+ children: [/* @__PURE__ */jsx(Icon, {
627
+ icon: "fluent:spinner-ios-20-regular"
628
+ }), "Processando..."]
629
+ }) : label
630
+ }, index);
631
+ })
632
+ });
633
+ };
634
+
635
+ //#endregion
636
+ //#region src/components/subscriptionPanel/SubscriptionPanel.tsx
637
+ /**
638
+ * Renders a metric card based on its type.
639
+ */
640
+ const renderMetricCard = params => {
641
+ const {
642
+ metric,
643
+ index,
644
+ isLoading
645
+ } = params;
646
+ return /* @__PURE__ */jsx(MetricCard, {
647
+ metric,
648
+ isLoading
649
+ }, index);
650
+ };
651
+ /**
652
+ * SubscriptionPanel displays comprehensive subscription information including
653
+ * plan details, status, actions, and various metrics.
654
+ *
655
+ * It supports three types of metrics:
656
+ * - **Date**: For displaying dates like expiration or renewal
657
+ * - **Percentage**: For usage-based metrics with progress bars
658
+ * - **Number**: For count-based metrics
659
+ *
660
+ * @example
661
+ * ```tsx
662
+ * <SubscriptionPanel
663
+ * planName="Premium Plan"
664
+ * price={{ value: "R$ 99,00", interval: "mês" }}
665
+ * status={{ status: "active", interval: "Mensal" }}
666
+ * features={[{ label: "Feature 1" }, { label: "Feature 2" }]}
667
+ * actions={[
668
+ * { label: "Upgrade", onClick: () => {}, variant: "accent" },
669
+ * { label: "Cancel", onClick: () => {}, variant: "danger" },
670
+ * ]}
671
+ * metrics={[
672
+ * { type: "date", label: "Expira em", date: "15/01/2025", icon: "fluent:calendar-24-regular" },
673
+ * { type: "percentage", label: "Uso", current: 75, max: 100, icon: "fluent:data-usage-24-regular" },
674
+ * ]}
675
+ * />
676
+ * ```
677
+ *
678
+ * @example
679
+ * ```tsx
680
+ * // Compact mode for smaller spaces
681
+ * <SubscriptionPanel
682
+ * planName="Basic Plan"
683
+ * price={{ value: "R$ 29,00", interval: "mês" }}
684
+ * status={{ status: "active" }}
685
+ * compact
686
+ * />
687
+ * ```
688
+ */
689
+ const SubscriptionPanel = ({
690
+ variant = "accent",
691
+ icon,
692
+ planName,
693
+ price,
694
+ status,
695
+ features = [],
696
+ actions = [],
697
+ metrics = [],
698
+ isLoading = false
699
+ }) => {
700
+ if (isLoading)
701
+ /**
702
+ * use skeleton loader in the future here
703
+ */
704
+ return /* @__PURE__ */jsx(Card, {
705
+ sx: {
706
+ width: "full",
707
+ height: "400px",
708
+ alignItems: "center",
709
+ justifyContent: "center"
710
+ },
711
+ children: /* @__PURE__ */jsx(Spinner, {})
712
+ });
713
+ return /* @__PURE__ */jsxs(Card, {
714
+ sx: {
715
+ width: "full"
716
+ },
717
+ children: [/* @__PURE__ */jsx(Flex, {
718
+ sx: getSubscriptionPanelAccentBarSx(variant)
719
+ }), /* @__PURE__ */jsxs(Flex, {
720
+ sx: {
721
+ width: "full",
722
+ paddingX: "6",
723
+ paddingY: "6",
724
+ flexDirection: "column"
725
+ },
726
+ children: [/* @__PURE__ */jsxs(Flex, {
727
+ sx: {
728
+ paddingY: "10",
729
+ paddingX: "3",
730
+ width: "full",
731
+ flexDirection: ["column", "column", "row"],
732
+ alignItems: ["stretch", "stretch", "center"],
733
+ justifyContent: "space-between",
734
+ gap: "6",
735
+ borderBottom: "sm",
736
+ borderBottomColor: "display.border.muted.default"
737
+ },
738
+ children: [/* @__PURE__ */jsx(EnhancedTitle, {
739
+ variant,
740
+ icon,
741
+ title: planName,
742
+ frontTitle: price.interval ? `${price.value}/${price.interval}` : price.value,
743
+ topBadges: [...(status.status === "active" ? [{
744
+ label: "Ativo",
745
+ variant: "positive",
746
+ icon: "fluent:checkmark-circle-24-filled"
747
+ }] : status.status === "inactive" ? [{
748
+ label: "Inativo",
749
+ variant: "muted",
750
+ icon: "fluent:dismiss-circle-24-filled"
751
+ }] : [{
752
+ label: "Cancelado",
753
+ variant: "negative",
754
+ icon: "fluent:error-circle-24-filled"
755
+ }]), ...(status.interval ? [{
756
+ label: status.interval,
757
+ variant: "informative"
758
+ }] : []), ...(status.hasScheduledUpdate ? [{
759
+ label: "Alteração Agendada",
760
+ variant: "informative",
761
+ icon: "fluent:clock-24-regular"
762
+ }] : []), ...(status.hasCancellation ? [{
763
+ label: "Renovação Cancelada",
764
+ variant: "negative",
765
+ icon: "fluent:dismiss-circle-24-regular"
766
+ }] : [])],
767
+ bottomBadges: features.map(feature => {
768
+ return {
769
+ label: feature.label,
770
+ icon: typeof feature.icon === "string" ? feature.icon : "fluent:checkmark-24-filled",
771
+ variant: "muted"
772
+ };
773
+ })
774
+ }), actions.length > 0 && /* @__PURE__ */jsx(SubscriptionPanelActionsSlot, {
775
+ actions
776
+ })]
777
+ }), metrics.length > 0 && /* @__PURE__ */jsx(Fragment, {
778
+ children: /* @__PURE__ */jsx(Flex, {
779
+ sx: {
780
+ paddingY: "10",
781
+ paddingX: "3",
782
+ width: "full",
783
+ flexWrap: "wrap",
784
+ gap: "10",
785
+ justifyContent: "center"
786
+ },
787
+ children: metrics.map((metric, index) => {
788
+ return renderMetricCard({
789
+ metric,
790
+ index,
791
+ isLoading
792
+ });
793
+ })
794
+ })
795
+ })]
796
+ })]
797
+ });
798
+ };
799
+
800
+ //#endregion
801
+ export { PlanCard, PlansPanel, SubscriptionPanel };