myoperator-ui 0.0.166 → 0.0.167-beta.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +1239 -17
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -8566,7 +8566,13 @@ import {
|
|
|
8566
8566
|
DialogTitle,
|
|
8567
8567
|
} from "../dialog";
|
|
8568
8568
|
import { PaymentOptionCard } from "./payment-option-card";
|
|
8569
|
-
import type {
|
|
8569
|
+
import type { PaymentOptionCardProps } from "./types";
|
|
8570
|
+
|
|
8571
|
+
interface PaymentOptionCardModalProps
|
|
8572
|
+
extends Omit<PaymentOptionCardProps, "onClose"> {
|
|
8573
|
+
open: boolean;
|
|
8574
|
+
onOpenChange: (open: boolean) => void;
|
|
8575
|
+
}
|
|
8570
8576
|
|
|
8571
8577
|
/**
|
|
8572
8578
|
* PaymentOptionCardModal wraps the PaymentOptionCard in a centered Dialog overlay.
|
|
@@ -8694,30 +8700,1246 @@ export interface PaymentOptionCardProps {
|
|
|
8694
8700
|
/** Additional className for the root element */
|
|
8695
8701
|
className?: string;
|
|
8696
8702
|
}
|
|
8703
|
+
`, prefix)
|
|
8704
|
+
},
|
|
8705
|
+
{
|
|
8706
|
+
name: "index.ts",
|
|
8707
|
+
content: prefixTailwindClasses(`export { PaymentOptionCard } from "./payment-option-card";
|
|
8708
|
+
export { PaymentOptionCardModal } from "./payment-option-card-modal";
|
|
8709
|
+
export type { PaymentOptionCardProps, PaymentOption } from "./types";
|
|
8710
|
+
`, prefix)
|
|
8711
|
+
}
|
|
8712
|
+
]
|
|
8713
|
+
},
|
|
8714
|
+
"let-us-drive-card": {
|
|
8715
|
+
name: "let-us-drive-card",
|
|
8716
|
+
description: "A managed service card with pricing, billing badge, 'Show details' link, and CTA for the full-service management section",
|
|
8717
|
+
category: "custom",
|
|
8718
|
+
dependencies: [
|
|
8719
|
+
"clsx",
|
|
8720
|
+
"tailwind-merge"
|
|
8721
|
+
],
|
|
8722
|
+
internalDependencies: [
|
|
8723
|
+
"button",
|
|
8724
|
+
"badge"
|
|
8725
|
+
],
|
|
8726
|
+
isMultiFile: true,
|
|
8727
|
+
directory: "let-us-drive-card",
|
|
8728
|
+
mainFile: "let-us-drive-card.tsx",
|
|
8729
|
+
files: [
|
|
8730
|
+
{
|
|
8731
|
+
name: "let-us-drive-card.tsx",
|
|
8732
|
+
content: prefixTailwindClasses(`import * as React from "react";
|
|
8733
|
+
import { cn } from "../../../lib/utils";
|
|
8734
|
+
import { Button } from "../button";
|
|
8735
|
+
import { Badge } from "../badge";
|
|
8736
|
+
import type { LetUsDriveCardProps } from "./types";
|
|
8697
8737
|
|
|
8698
8738
|
/**
|
|
8699
|
-
*
|
|
8700
|
-
*
|
|
8701
|
-
*
|
|
8739
|
+
* LetUsDriveCard displays a managed service offering with pricing, billing
|
|
8740
|
+
* frequency badge, and a CTA. Used in the "Let us drive \u2014 Full-service
|
|
8741
|
+
* management" section of the pricing page.
|
|
8742
|
+
*
|
|
8743
|
+
* Supports a "free/discount" state where the original price is shown with
|
|
8744
|
+
* strikethrough and a green label (e.g., "FREE") replaces it.
|
|
8745
|
+
*
|
|
8746
|
+
* @example
|
|
8747
|
+
* \`\`\`tsx
|
|
8748
|
+
* <LetUsDriveCard
|
|
8749
|
+
* title="Account Manager"
|
|
8750
|
+
* price="15,000"
|
|
8751
|
+
* period="/month"
|
|
8752
|
+
* billingBadge="Annually"
|
|
8753
|
+
* description="One expert who knows your business. And moves it forward."
|
|
8754
|
+
* onShowDetails={() => console.log("details")}
|
|
8755
|
+
* onCtaClick={() => console.log("talk")}
|
|
8756
|
+
* />
|
|
8757
|
+
* \`\`\`
|
|
8702
8758
|
*/
|
|
8703
|
-
|
|
8704
|
-
|
|
8705
|
-
|
|
8706
|
-
|
|
8707
|
-
|
|
8708
|
-
|
|
8759
|
+
const LetUsDriveCard = React.forwardRef<HTMLDivElement, LetUsDriveCardProps>(
|
|
8760
|
+
(
|
|
8761
|
+
{
|
|
8762
|
+
title,
|
|
8763
|
+
price,
|
|
8764
|
+
period,
|
|
8765
|
+
startsAt = false,
|
|
8766
|
+
billingBadge,
|
|
8767
|
+
description,
|
|
8768
|
+
freeLabel,
|
|
8769
|
+
showDetailsLabel = "Show details",
|
|
8770
|
+
ctaLabel = "Talk to us",
|
|
8771
|
+
onShowDetails,
|
|
8772
|
+
onCtaClick,
|
|
8773
|
+
className,
|
|
8774
|
+
...props
|
|
8775
|
+
},
|
|
8776
|
+
ref
|
|
8777
|
+
) => {
|
|
8778
|
+
return (
|
|
8779
|
+
<div
|
|
8780
|
+
ref={ref}
|
|
8781
|
+
className={cn(
|
|
8782
|
+
"flex flex-col gap-6 rounded-[14px] border border-semantic-border-layout bg-card p-5",
|
|
8783
|
+
className
|
|
8784
|
+
)}
|
|
8785
|
+
{...props}
|
|
8786
|
+
>
|
|
8787
|
+
{/* Header: title + optional billing badge */}
|
|
8788
|
+
<div className="flex items-center justify-between">
|
|
8789
|
+
<h3 className="text-base font-semibold text-semantic-text-primary m-0">
|
|
8790
|
+
{title}
|
|
8791
|
+
</h3>
|
|
8792
|
+
{billingBadge && (
|
|
8793
|
+
<Badge
|
|
8794
|
+
size="sm"
|
|
8795
|
+
className="bg-semantic-info-surface text-semantic-info-primary font-normal"
|
|
8796
|
+
>
|
|
8797
|
+
{billingBadge}
|
|
8798
|
+
</Badge>
|
|
8799
|
+
)}
|
|
8800
|
+
</div>
|
|
8801
|
+
|
|
8802
|
+
{/* Price section */}
|
|
8803
|
+
<div className="flex flex-col gap-2.5">
|
|
8804
|
+
{startsAt && (
|
|
8805
|
+
<span className="text-xs text-semantic-text-muted tracking-[0.048px]">
|
|
8806
|
+
Starts at
|
|
8807
|
+
</span>
|
|
8808
|
+
)}
|
|
8809
|
+
<div className="flex gap-1 items-end">
|
|
8810
|
+
{freeLabel ? (
|
|
8811
|
+
<span className="text-[28px] font-semibold leading-[36px]">
|
|
8812
|
+
<span className="line-through text-semantic-text-muted">
|
|
8813
|
+
\u20B9{price}
|
|
8814
|
+
</span>{" "}
|
|
8815
|
+
<span className="text-semantic-success-primary">
|
|
8816
|
+
{freeLabel}
|
|
8817
|
+
</span>
|
|
8818
|
+
</span>
|
|
8819
|
+
) : (
|
|
8820
|
+
<span className="text-[28px] font-semibold leading-[36px] text-semantic-text-primary">
|
|
8821
|
+
\u20B9{price}
|
|
8822
|
+
</span>
|
|
8823
|
+
)}
|
|
8824
|
+
{period && (
|
|
8825
|
+
<span className="text-sm text-semantic-text-muted tracking-[0.035px]">
|
|
8826
|
+
{period}
|
|
8827
|
+
</span>
|
|
8828
|
+
)}
|
|
8829
|
+
</div>
|
|
8830
|
+
|
|
8831
|
+
{/* Description */}
|
|
8832
|
+
<p className="text-sm text-semantic-text-secondary tracking-[0.035px] m-0">
|
|
8833
|
+
{description}
|
|
8834
|
+
</p>
|
|
8835
|
+
</div>
|
|
8836
|
+
|
|
8837
|
+
{/* Actions: Show details link + CTA button */}
|
|
8838
|
+
<div className="flex flex-col gap-3 w-full">
|
|
8839
|
+
{onShowDetails && (
|
|
8840
|
+
<Button
|
|
8841
|
+
variant="link"
|
|
8842
|
+
className="text-semantic-text-link p-0 h-auto min-w-0 justify-start"
|
|
8843
|
+
onClick={onShowDetails}
|
|
8844
|
+
>
|
|
8845
|
+
{showDetailsLabel}
|
|
8846
|
+
</Button>
|
|
8847
|
+
)}
|
|
8848
|
+
<Button variant="outline" className="w-full" onClick={onCtaClick}>
|
|
8849
|
+
{ctaLabel}
|
|
8850
|
+
</Button>
|
|
8851
|
+
</div>
|
|
8852
|
+
</div>
|
|
8853
|
+
);
|
|
8854
|
+
}
|
|
8855
|
+
);
|
|
8856
|
+
|
|
8857
|
+
LetUsDriveCard.displayName = "LetUsDriveCard";
|
|
8858
|
+
|
|
8859
|
+
export { LetUsDriveCard };
|
|
8860
|
+
`, prefix)
|
|
8861
|
+
},
|
|
8862
|
+
{
|
|
8863
|
+
name: "types.ts",
|
|
8864
|
+
content: prefixTailwindClasses(`import * as React from "react";
|
|
8865
|
+
|
|
8866
|
+
/**
|
|
8867
|
+
* Props for the LetUsDriveCard component.
|
|
8868
|
+
*/
|
|
8869
|
+
export interface LetUsDriveCardProps
|
|
8870
|
+
extends React.HTMLAttributes<HTMLDivElement> {
|
|
8871
|
+
/** Service title (e.g., "Dedicated Onboarding", "Account Manager") */
|
|
8872
|
+
title: string;
|
|
8873
|
+
/** Price amount as formatted string (e.g., "20,000", "15,000") */
|
|
8874
|
+
price: string;
|
|
8875
|
+
/** Billing period label (e.g., "/one-time fee", "/month") */
|
|
8876
|
+
period?: string;
|
|
8877
|
+
/** Show "Starts at" prefix above the price */
|
|
8878
|
+
startsAt?: boolean;
|
|
8879
|
+
/** Billing frequency badge text (e.g., "Annually", "Quarterly") */
|
|
8880
|
+
billingBadge?: string;
|
|
8881
|
+
/** Service description text */
|
|
8882
|
+
description: string;
|
|
8883
|
+
/** When provided, price is shown with strikethrough and this label (e.g., "FREE") is displayed in green */
|
|
8884
|
+
freeLabel?: string;
|
|
8885
|
+
/** Text for the details link (default: "Show details") */
|
|
8886
|
+
showDetailsLabel?: string;
|
|
8887
|
+
/** CTA button text (default: "Talk to us") */
|
|
8888
|
+
ctaLabel?: string;
|
|
8889
|
+
/** Callback when "Show details" link is clicked */
|
|
8890
|
+
onShowDetails?: () => void;
|
|
8891
|
+
/** Callback when CTA button is clicked */
|
|
8892
|
+
onCtaClick?: () => void;
|
|
8709
8893
|
}
|
|
8710
8894
|
`, prefix)
|
|
8711
8895
|
},
|
|
8712
8896
|
{
|
|
8713
8897
|
name: "index.ts",
|
|
8714
|
-
content: prefixTailwindClasses(`export {
|
|
8715
|
-
export {
|
|
8716
|
-
|
|
8717
|
-
|
|
8718
|
-
|
|
8719
|
-
|
|
8720
|
-
|
|
8898
|
+
content: prefixTailwindClasses(`export { LetUsDriveCard } from "./let-us-drive-card";
|
|
8899
|
+
export type { LetUsDriveCardProps } from "./types";
|
|
8900
|
+
`, prefix)
|
|
8901
|
+
}
|
|
8902
|
+
]
|
|
8903
|
+
},
|
|
8904
|
+
"power-up-card": {
|
|
8905
|
+
name: "power-up-card",
|
|
8906
|
+
description: "An add-on service card with icon, title, pricing, description, and CTA button for the power-ups section",
|
|
8907
|
+
category: "custom",
|
|
8908
|
+
dependencies: [
|
|
8909
|
+
"clsx",
|
|
8910
|
+
"tailwind-merge"
|
|
8911
|
+
],
|
|
8912
|
+
internalDependencies: [
|
|
8913
|
+
"button"
|
|
8914
|
+
],
|
|
8915
|
+
isMultiFile: true,
|
|
8916
|
+
directory: "power-up-card",
|
|
8917
|
+
mainFile: "power-up-card.tsx",
|
|
8918
|
+
files: [
|
|
8919
|
+
{
|
|
8920
|
+
name: "power-up-card.tsx",
|
|
8921
|
+
content: prefixTailwindClasses(`import * as React from "react";
|
|
8922
|
+
import { cn } from "../../../lib/utils";
|
|
8923
|
+
import { Button } from "../button";
|
|
8924
|
+
import type { PowerUpCardProps } from "./types";
|
|
8925
|
+
|
|
8926
|
+
/**
|
|
8927
|
+
* PowerUpCard displays an add-on service with icon, pricing, description,
|
|
8928
|
+
* and a CTA button. Used in the "Power-ups and charges" section of
|
|
8929
|
+
* the pricing page.
|
|
8930
|
+
*
|
|
8931
|
+
* @example
|
|
8932
|
+
* \`\`\`tsx
|
|
8933
|
+
* <PowerUpCard
|
|
8934
|
+
* icon={<PhoneCall className="size-6" />}
|
|
8935
|
+
* title="Auto-Dialer"
|
|
8936
|
+
* price="Starts @ \u20B9700/user/month"
|
|
8937
|
+
* description="Available for SUV & Enterprise plans as an add-on per user."
|
|
8938
|
+
* onCtaClick={() => console.log("clicked")}
|
|
8939
|
+
* />
|
|
8940
|
+
* \`\`\`
|
|
8941
|
+
*/
|
|
8942
|
+
const PowerUpCard = React.forwardRef<HTMLDivElement, PowerUpCardProps>(
|
|
8943
|
+
(
|
|
8944
|
+
{
|
|
8945
|
+
icon,
|
|
8946
|
+
title,
|
|
8947
|
+
price,
|
|
8948
|
+
description,
|
|
8949
|
+
ctaLabel = "Talk to us",
|
|
8950
|
+
onCtaClick,
|
|
8951
|
+
className,
|
|
8952
|
+
...props
|
|
8953
|
+
},
|
|
8954
|
+
ref
|
|
8955
|
+
) => {
|
|
8956
|
+
return (
|
|
8957
|
+
<div
|
|
8958
|
+
ref={ref}
|
|
8959
|
+
className={cn(
|
|
8960
|
+
"flex flex-col justify-between gap-8 rounded-md border border-semantic-border-layout bg-card p-5",
|
|
8961
|
+
className
|
|
8962
|
+
)}
|
|
8963
|
+
{...props}
|
|
8964
|
+
>
|
|
8965
|
+
{/* Content */}
|
|
8966
|
+
<div className="flex flex-col gap-4">
|
|
8967
|
+
{/* Icon + title/price row */}
|
|
8968
|
+
<div className="flex gap-4 items-start">
|
|
8969
|
+
{icon && (
|
|
8970
|
+
<div className="flex items-center justify-center size-[47px] rounded bg-[var(--color-info-25)] shrink-0">
|
|
8971
|
+
{icon}
|
|
8972
|
+
</div>
|
|
8973
|
+
)}
|
|
8974
|
+
<div className="flex flex-col gap-2 min-w-0">
|
|
8975
|
+
<h3 className="text-base font-semibold text-semantic-text-primary m-0 leading-normal">
|
|
8976
|
+
{title}
|
|
8977
|
+
</h3>
|
|
8978
|
+
<p className="text-sm text-semantic-text-primary tracking-[0.035px] m-0 leading-normal">
|
|
8979
|
+
{price}
|
|
8980
|
+
</p>
|
|
8981
|
+
</div>
|
|
8982
|
+
</div>
|
|
8983
|
+
|
|
8984
|
+
{/* Description */}
|
|
8985
|
+
<p className="text-sm text-semantic-text-secondary tracking-[0.035px] m-0 leading-normal">
|
|
8986
|
+
{description}
|
|
8987
|
+
</p>
|
|
8988
|
+
</div>
|
|
8989
|
+
|
|
8990
|
+
{/* CTA */}
|
|
8991
|
+
<Button variant="outline" className="w-full" onClick={onCtaClick}>
|
|
8992
|
+
{ctaLabel}
|
|
8993
|
+
</Button>
|
|
8994
|
+
</div>
|
|
8995
|
+
);
|
|
8996
|
+
}
|
|
8997
|
+
);
|
|
8998
|
+
|
|
8999
|
+
PowerUpCard.displayName = "PowerUpCard";
|
|
9000
|
+
|
|
9001
|
+
export { PowerUpCard };
|
|
9002
|
+
`, prefix)
|
|
9003
|
+
},
|
|
9004
|
+
{
|
|
9005
|
+
name: "types.ts",
|
|
9006
|
+
content: prefixTailwindClasses(`import * as React from "react";
|
|
9007
|
+
|
|
9008
|
+
/**
|
|
9009
|
+
* Props for the PowerUpCard component.
|
|
9010
|
+
*/
|
|
9011
|
+
export interface PowerUpCardProps
|
|
9012
|
+
extends React.HTMLAttributes<HTMLDivElement> {
|
|
9013
|
+
/** Icon or illustration displayed in the tinted container */
|
|
9014
|
+
icon?: React.ReactNode;
|
|
9015
|
+
/** Service title (e.g., "Truecaller business") */
|
|
9016
|
+
title: string;
|
|
9017
|
+
/** Pricing text (e.g., "Starts @ \u20B930,000/month") */
|
|
9018
|
+
price: string;
|
|
9019
|
+
/** Description explaining the service value */
|
|
9020
|
+
description: string;
|
|
9021
|
+
/** CTA button label (default: "Talk to us") */
|
|
9022
|
+
ctaLabel?: string;
|
|
9023
|
+
/** Callback when CTA button is clicked */
|
|
9024
|
+
onCtaClick?: () => void;
|
|
9025
|
+
}
|
|
9026
|
+
`, prefix)
|
|
9027
|
+
},
|
|
9028
|
+
{
|
|
9029
|
+
name: "index.ts",
|
|
9030
|
+
content: prefixTailwindClasses(`export { PowerUpCard } from "./power-up-card";
|
|
9031
|
+
export type { PowerUpCardProps } from "./types";
|
|
9032
|
+
`, prefix)
|
|
9033
|
+
}
|
|
9034
|
+
]
|
|
9035
|
+
},
|
|
9036
|
+
"pricing-card": {
|
|
9037
|
+
name: "pricing-card",
|
|
9038
|
+
description: "A pricing tier card with plan name, pricing, feature checklist, CTA button, and optional popularity badge and addon footer",
|
|
9039
|
+
category: "custom",
|
|
9040
|
+
dependencies: [
|
|
9041
|
+
"clsx",
|
|
9042
|
+
"tailwind-merge",
|
|
9043
|
+
"lucide-react"
|
|
9044
|
+
],
|
|
9045
|
+
internalDependencies: [
|
|
9046
|
+
"button",
|
|
9047
|
+
"badge"
|
|
9048
|
+
],
|
|
9049
|
+
isMultiFile: true,
|
|
9050
|
+
directory: "pricing-card",
|
|
9051
|
+
mainFile: "pricing-card.tsx",
|
|
9052
|
+
files: [
|
|
9053
|
+
{
|
|
9054
|
+
name: "pricing-card.tsx",
|
|
9055
|
+
content: prefixTailwindClasses(`import * as React from "react";
|
|
9056
|
+
import { cn } from "../../../lib/utils";
|
|
9057
|
+
import { Button } from "../button";
|
|
9058
|
+
import { Badge } from "../badge";
|
|
9059
|
+
import { CircleCheck } from "lucide-react";
|
|
9060
|
+
import type { PricingCardProps } from "./types";
|
|
9061
|
+
|
|
9062
|
+
/**
|
|
9063
|
+
* PricingCard displays a plan tier with pricing, features, and a CTA button.
|
|
9064
|
+
* Supports current-plan state (outlined button), popularity badge, and an
|
|
9065
|
+
* optional add-on footer.
|
|
9066
|
+
*
|
|
9067
|
+
* @example
|
|
9068
|
+
* \`\`\`tsx
|
|
9069
|
+
* <PricingCard
|
|
9070
|
+
* planName="Compact"
|
|
9071
|
+
* price="2,5000"
|
|
9072
|
+
* planDetails="3 Users | 12 Month plan"
|
|
9073
|
+
* description="For small teams that need a WhatsApp-first plan"
|
|
9074
|
+
* headerBgColor="#d7eae9"
|
|
9075
|
+
* features={["WhatsApp Campaigns", "Missed Call Tracking"]}
|
|
9076
|
+
* onCtaClick={() => console.log("selected")}
|
|
9077
|
+
* onFeatureDetails={() => console.log("details")}
|
|
9078
|
+
* />
|
|
9079
|
+
* \`\`\`
|
|
9080
|
+
*/
|
|
9081
|
+
const PricingCard = React.forwardRef<HTMLDivElement, PricingCardProps>(
|
|
9082
|
+
(
|
|
9083
|
+
{
|
|
9084
|
+
planName,
|
|
9085
|
+
price,
|
|
9086
|
+
period = "/Month",
|
|
9087
|
+
planDetails,
|
|
9088
|
+
planIcon,
|
|
9089
|
+
description,
|
|
9090
|
+
headerBgColor,
|
|
9091
|
+
features = [],
|
|
9092
|
+
isCurrentPlan = false,
|
|
9093
|
+
showPopularBadge = false,
|
|
9094
|
+
badgeText = "MOST POPULAR",
|
|
9095
|
+
ctaText,
|
|
9096
|
+
onCtaClick,
|
|
9097
|
+
onFeatureDetails,
|
|
9098
|
+
addon,
|
|
9099
|
+
usageDetails,
|
|
9100
|
+
className,
|
|
9101
|
+
...props
|
|
9102
|
+
},
|
|
9103
|
+
ref
|
|
9104
|
+
) => {
|
|
9105
|
+
const buttonText =
|
|
9106
|
+
ctaText || (isCurrentPlan ? "Current plan" : "Select plan");
|
|
9107
|
+
|
|
9108
|
+
return (
|
|
9109
|
+
<div
|
|
9110
|
+
ref={ref}
|
|
9111
|
+
className={cn(
|
|
9112
|
+
"flex flex-col gap-6 rounded-t-xl rounded-b-lg border border-semantic-border-layout p-4",
|
|
9113
|
+
className
|
|
9114
|
+
)}
|
|
9115
|
+
{...props}
|
|
9116
|
+
>
|
|
9117
|
+
{/* Header */}
|
|
9118
|
+
<div
|
|
9119
|
+
className="flex flex-col gap-4 rounded-t-xl rounded-b-lg p-4"
|
|
9120
|
+
style={
|
|
9121
|
+
headerBgColor ? { backgroundColor: headerBgColor } : undefined
|
|
9122
|
+
}
|
|
9123
|
+
>
|
|
9124
|
+
{/* Plan name + badge */}
|
|
9125
|
+
<div className="flex items-center gap-4">
|
|
9126
|
+
<h3 className="text-xl font-semibold text-semantic-text-primary m-0">
|
|
9127
|
+
{planName}
|
|
9128
|
+
</h3>
|
|
9129
|
+
{showPopularBadge && (
|
|
9130
|
+
<Badge
|
|
9131
|
+
size="sm"
|
|
9132
|
+
className="bg-[#e3fdfe] text-[#119ba8] uppercase tracking-wider font-semibold"
|
|
9133
|
+
>
|
|
9134
|
+
{badgeText}
|
|
9135
|
+
</Badge>
|
|
9136
|
+
)}
|
|
9137
|
+
</div>
|
|
9138
|
+
|
|
9139
|
+
{/* Price */}
|
|
9140
|
+
<div className="flex flex-col gap-2.5">
|
|
9141
|
+
<div className="flex items-end gap-1">
|
|
9142
|
+
<span className="text-4xl leading-[44px] text-semantic-text-primary">
|
|
9143
|
+
\u20B9{price}
|
|
9144
|
+
</span>
|
|
9145
|
+
<span className="text-sm text-semantic-text-muted tracking-[0.035px]">
|
|
9146
|
+
{period}
|
|
9147
|
+
</span>
|
|
9148
|
+
</div>
|
|
9149
|
+
{planDetails && (
|
|
9150
|
+
<p className="text-sm tracking-[0.035px] text-semantic-text-primary m-0">
|
|
9151
|
+
{planDetails}
|
|
9152
|
+
</p>
|
|
9153
|
+
)}
|
|
9154
|
+
</div>
|
|
9155
|
+
|
|
9156
|
+
{/* Plan icon */}
|
|
9157
|
+
{planIcon && <div className="size-[30px]">{planIcon}</div>}
|
|
9158
|
+
|
|
9159
|
+
{/* Description */}
|
|
9160
|
+
{description && (
|
|
9161
|
+
<p className="text-sm text-semantic-text-secondary tracking-[0.035px] m-0">
|
|
9162
|
+
{description}
|
|
9163
|
+
</p>
|
|
9164
|
+
)}
|
|
9165
|
+
|
|
9166
|
+
{/* Feature details link + CTA */}
|
|
9167
|
+
<div className="flex flex-col gap-3.5 w-full">
|
|
9168
|
+
{onFeatureDetails && (
|
|
9169
|
+
<div className="flex justify-center">
|
|
9170
|
+
<Button
|
|
9171
|
+
variant="link"
|
|
9172
|
+
className="text-semantic-text-link p-0 h-auto min-w-0"
|
|
9173
|
+
onClick={onFeatureDetails}
|
|
9174
|
+
>
|
|
9175
|
+
Feature details
|
|
9176
|
+
</Button>
|
|
9177
|
+
</div>
|
|
9178
|
+
)}
|
|
9179
|
+
<Button
|
|
9180
|
+
variant={isCurrentPlan ? "outline" : "default"}
|
|
9181
|
+
className="w-full"
|
|
9182
|
+
onClick={onCtaClick}
|
|
9183
|
+
>
|
|
9184
|
+
{buttonText}
|
|
9185
|
+
</Button>
|
|
9186
|
+
</div>
|
|
9187
|
+
</div>
|
|
9188
|
+
|
|
9189
|
+
{/* Features */}
|
|
9190
|
+
{features.length > 0 && (
|
|
9191
|
+
<div className="flex flex-col gap-4">
|
|
9192
|
+
<p className="text-sm font-semibold text-semantic-text-primary tracking-[0.014px] uppercase m-0">
|
|
9193
|
+
Includes
|
|
9194
|
+
</p>
|
|
9195
|
+
<div className="flex flex-col gap-4">
|
|
9196
|
+
{features.map((feature, index) => {
|
|
9197
|
+
const text =
|
|
9198
|
+
typeof feature === "string" ? feature : feature.text;
|
|
9199
|
+
const isBold =
|
|
9200
|
+
typeof feature !== "string" && feature.bold;
|
|
9201
|
+
return (
|
|
9202
|
+
<div key={index} className="flex items-start gap-2">
|
|
9203
|
+
<CircleCheck className="size-[18px] text-semantic-text-secondary shrink-0 mt-0.5" />
|
|
9204
|
+
<span
|
|
9205
|
+
className={cn(
|
|
9206
|
+
"text-sm text-semantic-text-secondary tracking-[0.035px]",
|
|
9207
|
+
isBold && "font-semibold"
|
|
9208
|
+
)}
|
|
9209
|
+
>
|
|
9210
|
+
{text}
|
|
9211
|
+
</span>
|
|
9212
|
+
</div>
|
|
9213
|
+
);
|
|
9214
|
+
})}
|
|
9215
|
+
</div>
|
|
9216
|
+
</div>
|
|
9217
|
+
)}
|
|
9218
|
+
|
|
9219
|
+
{/* Addon */}
|
|
9220
|
+
{addon && (
|
|
9221
|
+
<div className="flex items-center gap-2.5 rounded-md bg-[var(--color-info-25)] border border-[#f3f5f6] pl-4 py-2.5">
|
|
9222
|
+
{addon.icon && (
|
|
9223
|
+
<div className="size-5 shrink-0">{addon.icon}</div>
|
|
9224
|
+
)}
|
|
9225
|
+
<span className="text-sm text-semantic-text-primary tracking-[0.035px]">
|
|
9226
|
+
{addon.text}
|
|
9227
|
+
</span>
|
|
9228
|
+
</div>
|
|
9229
|
+
)}
|
|
9230
|
+
|
|
9231
|
+
{/* Usage Details */}
|
|
9232
|
+
{usageDetails && usageDetails.length > 0 && (
|
|
9233
|
+
<div className="flex flex-col gap-2.5 rounded-md bg-[var(--color-info-25)] border border-[#f3f5f6] px-4 py-2.5">
|
|
9234
|
+
{usageDetails.map((detail, index) => (
|
|
9235
|
+
<div key={index} className="flex items-start gap-2">
|
|
9236
|
+
<span className="size-1.5 rounded-full bg-semantic-primary shrink-0 mt-[7px]" />
|
|
9237
|
+
<span className="text-sm text-semantic-text-primary tracking-[0.035px]">
|
|
9238
|
+
<strong>{detail.label}:</strong> {detail.value}
|
|
9239
|
+
</span>
|
|
9240
|
+
</div>
|
|
9241
|
+
))}
|
|
9242
|
+
</div>
|
|
9243
|
+
)}
|
|
9244
|
+
</div>
|
|
9245
|
+
);
|
|
9246
|
+
}
|
|
9247
|
+
);
|
|
9248
|
+
|
|
9249
|
+
PricingCard.displayName = "PricingCard";
|
|
9250
|
+
|
|
9251
|
+
export { PricingCard };
|
|
9252
|
+
`, prefix)
|
|
9253
|
+
},
|
|
9254
|
+
{
|
|
9255
|
+
name: "plan-icons.tsx",
|
|
9256
|
+
content: prefixTailwindClasses(`import * as React from "react";
|
|
9257
|
+
|
|
9258
|
+
interface PlanIconProps extends React.SVGAttributes<SVGElement> {
|
|
9259
|
+
className?: string;
|
|
9260
|
+
}
|
|
9261
|
+
|
|
9262
|
+
const CompactCarIcon = React.forwardRef<SVGSVGElement, PlanIconProps>(
|
|
9263
|
+
({ className, ...props }, ref) => (
|
|
9264
|
+
<svg
|
|
9265
|
+
ref={ref}
|
|
9266
|
+
className={className}
|
|
9267
|
+
viewBox="0 0 30 19"
|
|
9268
|
+
fill="none"
|
|
9269
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
9270
|
+
{...props}
|
|
9271
|
+
>
|
|
9272
|
+
<ellipse cx="25.2" cy="14.72" rx="3.33" ry="3.03" fill="white" />
|
|
9273
|
+
<path
|
|
9274
|
+
d="M25.12 11.21c-1.95 0-3.5 1.56-3.5 3.5 0 1.95 1.55 3.5 3.5 3.5 1.94 0 3.5-1.55 3.5-3.5 0-1.94-1.56-3.5-3.5-3.5zm0 5.45c-1.09 0-2.02-.93-2.02-2.02s.93-2.02 2.02-2.02 2.02.93 2.02 2.02-.93 2.02-2.02 2.02z"
|
|
9275
|
+
stroke="#2BBAC8"
|
|
9276
|
+
strokeLinejoin="round"
|
|
9277
|
+
/>
|
|
9278
|
+
<ellipse cx="4.09" cy="14.72" rx="3.33" ry="3.03" fill="white" />
|
|
9279
|
+
<path
|
|
9280
|
+
d="M4.26 11.21c-1.95 0-3.5 1.56-3.5 3.5 0 1.95 1.55 3.5 3.5 3.5 1.94 0 3.5-1.55 3.5-3.5 0-1.94-1.56-3.5-3.5-3.5zm0 5.45c-1.09 0-2.02-.93-2.02-2.02s.93-2.02 2.02-2.02 2.02.93 2.02 2.02-.93 2.02-2.02 2.02z"
|
|
9281
|
+
stroke="#2BBAC8"
|
|
9282
|
+
strokeLinejoin="round"
|
|
9283
|
+
/>
|
|
9284
|
+
<path
|
|
9285
|
+
d="M28.85 12.38c-.08-.16-.31-.31-.39-.47-.16-.39-.16-1.09-.23-1.48-.31-1.17-1.17-2.02-2.02-2.72-1.64-1.25-3.66-2.57-5.45-3.74C18 2.11 15.85.78 12.35.63c-1.79-.08-4.51 0-6.23.23-.15 0-1.4.31-1.24.62 1.25.23.55.93.24 1.63-.31.62-1.09 2.49-1.64 2.8-.15 0-.23.08-.31 0-.23-.31.16-1.4.31-1.71.16-.47.86-1.4.93-1.79 0-.31 0-.7-.39-.62L2.62 4.75c-.62 1.17-.62 2.18-.78 3.42-.15 1.56-1.09 2.88-1.24 4.36 0 .16 0 .39 0 .54.08.31.23.31.47.08.54-1.17 1.71-2.02 3.11-2.02 1.4 0 3.5 1.56 3.5 3.5s-.08 1.86-.23 2.25l.85-.08h11.75c.78-.08 1.4-.47 1.56-1.24 0-1.79 1.56-3.35 3.5-3.35 1.95 0 3.5 1.56 3.5 3.5 0 1.95-.16.93 0 1.09 1.09-.54.86-2.57.23-3.35v-.08z"
|
|
9286
|
+
fill="white"
|
|
9287
|
+
stroke="currentColor"
|
|
9288
|
+
strokeWidth="1.2"
|
|
9289
|
+
strokeLinejoin="round"
|
|
9290
|
+
/>
|
|
9291
|
+
<path
|
|
9292
|
+
d="M10.02 1.41c3.81-.23 8.56 1.4 11.44 3.89 2.88 2.49 1.79 1.64.16 1.79-3.58-.31-7.16-.62-10.74-.93-.86 0-2.65 0-3.27-.47-.62-.47-.54-1.87-.23-2.72.54-1.25 1.4-1.48 2.64-1.56z"
|
|
9293
|
+
stroke="currentColor"
|
|
9294
|
+
strokeWidth="1.2"
|
|
9295
|
+
strokeLinejoin="round"
|
|
9296
|
+
/>
|
|
9297
|
+
</svg>
|
|
9298
|
+
)
|
|
9299
|
+
);
|
|
9300
|
+
CompactCarIcon.displayName = "CompactCarIcon";
|
|
9301
|
+
|
|
9302
|
+
const SedanCarIcon = React.forwardRef<SVGSVGElement, PlanIconProps>(
|
|
9303
|
+
({ className, ...props }, ref) => (
|
|
9304
|
+
<svg
|
|
9305
|
+
ref={ref}
|
|
9306
|
+
className={className}
|
|
9307
|
+
viewBox="0 0 31 13"
|
|
9308
|
+
fill="none"
|
|
9309
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
9310
|
+
{...props}
|
|
9311
|
+
>
|
|
9312
|
+
<ellipse cx="24.98" cy="9.51" rx="2.19" ry="2.56" fill="white" />
|
|
9313
|
+
<path
|
|
9314
|
+
d="M24.8 7.16c-1.33 0-2.38 1.09-2.38 2.45 0 1.37 1.05 2.46 2.38 2.46 1.33 0 2.38-1.09 2.38-2.46 0-1.36-1.05-2.45-2.38-2.45zm0 3.82c-.74 0-1.38-.66-1.38-1.42 0-.76.64-1.42 1.38-1.42.74 0 1.38.66 1.38 1.42 0 .76-.64 1.42-1.38 1.42z"
|
|
9315
|
+
stroke="#2BBAC8"
|
|
9316
|
+
strokeLinejoin="round"
|
|
9317
|
+
/>
|
|
9318
|
+
<ellipse cx="6.33" cy="9.51" rx="2.19" ry="2.56" fill="white" />
|
|
9319
|
+
<path
|
|
9320
|
+
d="M6.32 7.16c-1.32 0-2.38 1.09-2.38 2.45 0 1.37 1.06 2.46 2.38 2.46 1.33 0 2.39-1.09 2.39-2.46 0-1.36-1.06-2.45-2.39-2.45zm0 3.82c-.74 0-1.38-.66-1.38-1.42 0-.76.64-1.42 1.38-1.42.74 0 1.38.66 1.38 1.42 0 .76-.64 1.42-1.38 1.42z"
|
|
9321
|
+
stroke="#2BBAC8"
|
|
9322
|
+
strokeLinejoin="round"
|
|
9323
|
+
/>
|
|
9324
|
+
<path
|
|
9325
|
+
d="M29.7 7.79l-.24.81c0 .08-.16.4-.23.49-.24.16-1.97.57-2.05.49.24-1.22-.47-2.6-1.57-3 -2.05-.81-4.09 1.05-3.54 3.24H8.99v-.81c0-.32-.39-1.05-.55-1.3C7.03 5.6 4.27 6.33 3.8 8.6c-.47 2.27 0 .49 0 .49l-2.28-.41C.81 8.27.49 7.14.73 6.33c.23-.81.39-.57.39-.73.08-.49-.16-1.62.16-2.03.31-.4 1.97-.4 2.44-.57 1.42-.4 2.76-1.22 4.17-1.62 2.91-.89 6.61-1.05 9.53 0 2.91 1.05 3.7 1.95 5.51 2.51 1.81.57 4.09.65 5.83 1.62 1.73.97.62 1.05.93 1.78v.57z"
|
|
9326
|
+
fill="white"
|
|
9327
|
+
stroke="currentColor"
|
|
9328
|
+
strokeWidth="1.3"
|
|
9329
|
+
strokeLinejoin="round"
|
|
9330
|
+
/>
|
|
9331
|
+
<path
|
|
9332
|
+
d="M13.48 1.38l.63 2.6 4.8.16c0-.32 0-.64.32-.89-1.58-1.38-3.78-1.78-5.83-1.87h.08z"
|
|
9333
|
+
stroke="currentColor"
|
|
9334
|
+
strokeWidth="1.3"
|
|
9335
|
+
strokeLinejoin="round"
|
|
9336
|
+
/>
|
|
9337
|
+
<path
|
|
9338
|
+
d="M8.99 1.87s-.63.97-.63 1.05c0 .16.16.65.24.81l4.41.16-.39-2.51c-.87 0-1.81 0-2.68.16-.87.16-.87.16-.95.24v.09z"
|
|
9339
|
+
stroke="currentColor"
|
|
9340
|
+
strokeWidth="1.3"
|
|
9341
|
+
strokeLinejoin="round"
|
|
9342
|
+
/>
|
|
9343
|
+
<path
|
|
9344
|
+
d="M6.08 3.81h1.18l1.26-1.78c-.47.32-2.2.81-2.36 1.3-.16.49 0 .32 0 .49h-.08z"
|
|
9345
|
+
stroke="currentColor"
|
|
9346
|
+
strokeWidth="1.3"
|
|
9347
|
+
strokeLinejoin="round"
|
|
9348
|
+
/>
|
|
9349
|
+
</svg>
|
|
9350
|
+
)
|
|
9351
|
+
);
|
|
9352
|
+
SedanCarIcon.displayName = "SedanCarIcon";
|
|
9353
|
+
|
|
9354
|
+
const SuvCarIcon = React.forwardRef<SVGSVGElement, PlanIconProps>(
|
|
9355
|
+
({ className, ...props }, ref) => (
|
|
9356
|
+
<svg
|
|
9357
|
+
ref={ref}
|
|
9358
|
+
className={className}
|
|
9359
|
+
viewBox="0 0 32 15"
|
|
9360
|
+
fill="none"
|
|
9361
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
9362
|
+
{...props}
|
|
9363
|
+
>
|
|
9364
|
+
<ellipse cx="25.57" cy="11.14" rx="2.65" ry="2.78" fill="white" />
|
|
9365
|
+
<ellipse cx="9.12" cy="11.14" rx="2.89" ry="2.78" fill="white" />
|
|
9366
|
+
<path
|
|
9367
|
+
d="M25.32 8.18c-1.61 0-2.9 1.3-2.9 2.94 0 1.63 1.29 2.93 2.9 2.93 1.62 0 2.9-1.3 2.9-2.93 0-1.64-1.28-2.94-2.9-2.94zm0 4.57c-.9 0-1.68-.78-1.68-1.7 0-.91.78-1.69 1.68-1.69.9 0 1.68.78 1.68 1.7 0 .91-.78 1.69-1.68 1.69z"
|
|
9368
|
+
stroke="#2BBAC8"
|
|
9369
|
+
strokeWidth="1.2"
|
|
9370
|
+
strokeLinejoin="round"
|
|
9371
|
+
/>
|
|
9372
|
+
<ellipse cx="9.14" cy="11.09" rx="1.4" ry="1.37" fill="white" />
|
|
9373
|
+
<path
|
|
9374
|
+
d="M8.96 8.18c-1.61 0-2.9 1.3-2.9 2.94 0 1.63 1.29 2.93 2.9 2.93 1.61 0 2.9-1.3 2.9-2.93 0-1.64-1.29-2.94-2.9-2.94zm0 4.57c-.9 0-1.68-.78-1.68-1.7 0-.91.78-1.69 1.68-1.69.9 0 1.68.78 1.68 1.7 0 .91-.78 1.69-1.68 1.69z"
|
|
9375
|
+
stroke="#2BBAC8"
|
|
9376
|
+
strokeWidth="1.2"
|
|
9377
|
+
strokeLinejoin="round"
|
|
9378
|
+
/>
|
|
9379
|
+
<path
|
|
9380
|
+
d="M30.6 10.78l-.26.99c-.3 1-.36.56-1.09.64.48-3.15-2.66-5.79-5.13-3.7-.43.37-1.18 1.52-1.18 2.1v1.5H12.06c.33-2.62-1.84-5.01-4.24-4.06-1.53.61-2.13 2.39-1.98 4.06-1.61-.14-3.18.68-3.39-1.7-.05-.6.07-1.21-.04-1.8-.65-.34-1.63.37-1.75-.77C.57 7.13.59 4.97.67 4.03c.03-.33.06-.79.43-.87.28-.06 1.83-.08 1.83.26v1.49l.29-.06c.67-1.75.59-3.97 2.76-4.15 3.76-.3 7.87.23 11.67.02 1.75.22 4.02 3.02 5.39 4.18 1.24.15 2.5.24 3.73.44.5.09 1.95.3 2.31.56.7.49.57 2.79.67 2.91.02.03.37.04.56.26.19.23.12.47.28.67v1.03z"
|
|
9381
|
+
fill="white"
|
|
9382
|
+
stroke="currentColor"
|
|
9383
|
+
strokeWidth="1.2"
|
|
9384
|
+
strokeLinejoin="round"
|
|
9385
|
+
/>
|
|
9386
|
+
<path
|
|
9387
|
+
d="M14.32 1.53c.25 1.41.16 2.98.61 4.32h6.22l.1-.21c-.48-1.34-1.41-2.72-2.51-3.53-.15-.11-.86-.59-.98-.59h-3.44z"
|
|
9388
|
+
stroke="currentColor"
|
|
9389
|
+
strokeWidth="1.2"
|
|
9390
|
+
strokeLinejoin="round"
|
|
9391
|
+
/>
|
|
9392
|
+
<path
|
|
9393
|
+
d="M9.71 1.53l-.19 4.32h4.33c-.17-1.3-.17-2.78-.38-4.06-.02-.12-.04-.2-.14-.26H9.71z"
|
|
9394
|
+
stroke="currentColor"
|
|
9395
|
+
strokeWidth="1.2"
|
|
9396
|
+
strokeLinejoin="round"
|
|
9397
|
+
/>
|
|
9398
|
+
<path
|
|
9399
|
+
d="M8.58 5.84l.29-4.07-.09-.31c-1.1.07-2.89-.32-3.46.95-.23.51-.74 2.67-.74 3.2 0 .12.01.13.1.21h3.91v-.01z"
|
|
9400
|
+
stroke="currentColor"
|
|
9401
|
+
strokeWidth="1.2"
|
|
9402
|
+
strokeLinejoin="round"
|
|
9403
|
+
/>
|
|
9404
|
+
</svg>
|
|
9405
|
+
)
|
|
9406
|
+
);
|
|
9407
|
+
SuvCarIcon.displayName = "SuvCarIcon";
|
|
9408
|
+
|
|
9409
|
+
export { CompactCarIcon, SedanCarIcon, SuvCarIcon };
|
|
9410
|
+
`, prefix)
|
|
9411
|
+
},
|
|
9412
|
+
{
|
|
9413
|
+
name: "types.ts",
|
|
9414
|
+
content: prefixTailwindClasses(`import * as React from "react";
|
|
9415
|
+
|
|
9416
|
+
/**
|
|
9417
|
+
* Add-on info displayed at the bottom of the pricing card.
|
|
9418
|
+
*/
|
|
9419
|
+
export interface PricingCardAddon {
|
|
9420
|
+
/** Icon rendered in the addon section */
|
|
9421
|
+
icon?: React.ReactNode;
|
|
9422
|
+
/** Addon description text */
|
|
9423
|
+
text: string;
|
|
9424
|
+
}
|
|
9425
|
+
|
|
9426
|
+
/**
|
|
9427
|
+
* A single usage detail item (e.g., "Usage: Includes 2,000 AI conversations/month").
|
|
9428
|
+
*/
|
|
9429
|
+
export interface UsageDetail {
|
|
9430
|
+
/** Bold label (e.g., "Usage") */
|
|
9431
|
+
label: string;
|
|
9432
|
+
/** Value text (e.g., "Includes 2,000 AI conversations/month") */
|
|
9433
|
+
value: string;
|
|
9434
|
+
}
|
|
9435
|
+
|
|
9436
|
+
/**
|
|
9437
|
+
* A feature can be a plain string or an object with bold styling.
|
|
9438
|
+
*/
|
|
9439
|
+
export type PricingCardFeature = string | { text: string; bold?: boolean };
|
|
9440
|
+
|
|
9441
|
+
/**
|
|
9442
|
+
* Props for the PricingCard component.
|
|
9443
|
+
*/
|
|
9444
|
+
export interface PricingCardProps
|
|
9445
|
+
extends React.HTMLAttributes<HTMLDivElement> {
|
|
9446
|
+
/** Plan name displayed in the header (e.g., "Compact", "Sedan", "SUV") */
|
|
9447
|
+
planName: string;
|
|
9448
|
+
/** Price amount as formatted string (e.g., "2,5000") */
|
|
9449
|
+
price: string;
|
|
9450
|
+
/** Billing period label (default: "/Month") */
|
|
9451
|
+
period?: string;
|
|
9452
|
+
/** Plan detail line (e.g., "3 Users | 12 Month plan") */
|
|
9453
|
+
planDetails?: React.ReactNode;
|
|
9454
|
+
/** Plan icon or illustration */
|
|
9455
|
+
planIcon?: React.ReactNode;
|
|
9456
|
+
/** Plan description text */
|
|
9457
|
+
description?: string;
|
|
9458
|
+
/** Background color for the header section */
|
|
9459
|
+
headerBgColor?: string;
|
|
9460
|
+
/** List of included features shown with checkmarks. Supports bold items via object form. */
|
|
9461
|
+
features?: PricingCardFeature[];
|
|
9462
|
+
/** Whether this is the currently active plan (shows outlined button) */
|
|
9463
|
+
isCurrentPlan?: boolean;
|
|
9464
|
+
/** Show a popularity badge next to the plan name */
|
|
9465
|
+
showPopularBadge?: boolean;
|
|
9466
|
+
/** Custom badge text (defaults to "MOST POPULAR") */
|
|
9467
|
+
badgeText?: string;
|
|
9468
|
+
/** Custom CTA button text (overrides default "Select plan" / "Current plan") */
|
|
9469
|
+
ctaText?: string;
|
|
9470
|
+
/** Callback when CTA button is clicked */
|
|
9471
|
+
onCtaClick?: () => void;
|
|
9472
|
+
/** Callback when "Feature details" link is clicked */
|
|
9473
|
+
onFeatureDetails?: () => void;
|
|
9474
|
+
/** Add-on info displayed at the bottom of the card */
|
|
9475
|
+
addon?: PricingCardAddon;
|
|
9476
|
+
/** Usage details displayed in a bulleted list at the bottom (e.g., AIO plan) */
|
|
9477
|
+
usageDetails?: UsageDetail[];
|
|
9478
|
+
}
|
|
9479
|
+
`, prefix)
|
|
9480
|
+
},
|
|
9481
|
+
{
|
|
9482
|
+
name: "index.ts",
|
|
9483
|
+
content: prefixTailwindClasses(`export { PricingCard } from "./pricing-card";
|
|
9484
|
+
export { CompactCarIcon, SedanCarIcon, SuvCarIcon } from "./plan-icons";
|
|
9485
|
+
export type {
|
|
9486
|
+
PricingCardProps,
|
|
9487
|
+
PricingCardAddon,
|
|
9488
|
+
PricingCardFeature,
|
|
9489
|
+
UsageDetail,
|
|
9490
|
+
} from "./types";
|
|
9491
|
+
`, prefix)
|
|
9492
|
+
}
|
|
9493
|
+
]
|
|
9494
|
+
},
|
|
9495
|
+
"pricing-page": {
|
|
9496
|
+
name: "pricing-page",
|
|
9497
|
+
description: "A full pricing page layout composing plan-type tabs, billing toggle, pricing cards grid, power-ups section, and let-us-drive managed services section",
|
|
9498
|
+
category: "custom",
|
|
9499
|
+
dependencies: [
|
|
9500
|
+
"clsx",
|
|
9501
|
+
"tailwind-merge",
|
|
9502
|
+
"lucide-react"
|
|
9503
|
+
],
|
|
9504
|
+
internalDependencies: [
|
|
9505
|
+
"button",
|
|
9506
|
+
"page-header",
|
|
9507
|
+
"pricing-toggle",
|
|
9508
|
+
"pricing-card",
|
|
9509
|
+
"power-up-card",
|
|
9510
|
+
"let-us-drive-card"
|
|
9511
|
+
],
|
|
9512
|
+
isMultiFile: true,
|
|
9513
|
+
directory: "pricing-page",
|
|
9514
|
+
mainFile: "pricing-page.tsx",
|
|
9515
|
+
files: [
|
|
9516
|
+
{
|
|
9517
|
+
name: "pricing-page.tsx",
|
|
9518
|
+
content: prefixTailwindClasses(`import * as React from "react";
|
|
9519
|
+
import { cn } from "../../../lib/utils";
|
|
9520
|
+
import { PageHeader } from "../page-header";
|
|
9521
|
+
import { Button } from "../button";
|
|
9522
|
+
import { PricingToggle } from "../pricing-toggle/pricing-toggle";
|
|
9523
|
+
import { PricingCard } from "../pricing-card/pricing-card";
|
|
9524
|
+
import { PowerUpCard } from "../power-up-card/power-up-card";
|
|
9525
|
+
import { LetUsDriveCard } from "../let-us-drive-card/let-us-drive-card";
|
|
9526
|
+
import { ExternalLink } from "lucide-react";
|
|
9527
|
+
import type { PricingPageProps } from "./types";
|
|
9528
|
+
|
|
9529
|
+
/**
|
|
9530
|
+
* PricingPage composes all plan-selection sub-components into a full
|
|
9531
|
+
* page layout: header, plan-type tabs with billing toggle, pricing
|
|
9532
|
+
* cards grid, power-ups section, and let-us-drive section.
|
|
9533
|
+
*
|
|
9534
|
+
* Supports controlled or uncontrolled tab / billing state.
|
|
9535
|
+
*
|
|
9536
|
+
* @example
|
|
9537
|
+
* \`\`\`tsx
|
|
9538
|
+
* <PricingPage
|
|
9539
|
+
* tabs={[
|
|
9540
|
+
* { label: "Team-Led Plans", value: "team" },
|
|
9541
|
+
* { label: "Go-AI First", value: "ai" },
|
|
9542
|
+
* ]}
|
|
9543
|
+
* planCards={compactCard, sedanCard, suvCard}
|
|
9544
|
+
* powerUpCards={[truecaller, tollFree, autoDialer]}
|
|
9545
|
+
* letUsDriveCards={[onboarding, accountMgr, managed]}
|
|
9546
|
+
* />
|
|
9547
|
+
* \`\`\`
|
|
9548
|
+
*/
|
|
9549
|
+
const PricingPage = React.forwardRef<HTMLDivElement, PricingPageProps>(
|
|
9550
|
+
(
|
|
9551
|
+
{
|
|
9552
|
+
title = "Select business plan",
|
|
9553
|
+
headerActions,
|
|
9554
|
+
tabs = [],
|
|
9555
|
+
activeTab: controlledTab,
|
|
9556
|
+
onTabChange,
|
|
9557
|
+
showBillingToggle = false,
|
|
9558
|
+
billingPeriod: controlledBilling,
|
|
9559
|
+
onBillingPeriodChange,
|
|
9560
|
+
planCards = [],
|
|
9561
|
+
powerUpCards = [],
|
|
9562
|
+
powerUpsTitle = "Power-ups and charges",
|
|
9563
|
+
featureComparisonText = "See full feature comparison",
|
|
9564
|
+
onFeatureComparisonClick,
|
|
9565
|
+
letUsDriveCards = [],
|
|
9566
|
+
letUsDriveTitle = "Let us drive \u2014 Full-service management",
|
|
9567
|
+
className,
|
|
9568
|
+
...props
|
|
9569
|
+
},
|
|
9570
|
+
ref
|
|
9571
|
+
) => {
|
|
9572
|
+
// Internal state for uncontrolled mode
|
|
9573
|
+
const [internalTab, setInternalTab] = React.useState(
|
|
9574
|
+
tabs[0]?.value ?? ""
|
|
9575
|
+
);
|
|
9576
|
+
const [internalBilling, setInternalBilling] = React.useState<
|
|
9577
|
+
"monthly" | "yearly"
|
|
9578
|
+
>("monthly");
|
|
9579
|
+
|
|
9580
|
+
const currentTab = controlledTab ?? internalTab;
|
|
9581
|
+
const currentBilling = controlledBilling ?? internalBilling;
|
|
9582
|
+
|
|
9583
|
+
const handleTabChange = (value: string) => {
|
|
9584
|
+
if (!controlledTab) setInternalTab(value);
|
|
9585
|
+
onTabChange?.(value);
|
|
9586
|
+
};
|
|
9587
|
+
|
|
9588
|
+
const handleBillingChange = (period: "monthly" | "yearly") => {
|
|
9589
|
+
if (!controlledBilling) setInternalBilling(period);
|
|
9590
|
+
onBillingPeriodChange?.(period);
|
|
9591
|
+
};
|
|
9592
|
+
|
|
9593
|
+
const hasPowerUps = powerUpCards.length > 0;
|
|
9594
|
+
const hasLetUsDrive = letUsDriveCards.length > 0;
|
|
9595
|
+
|
|
9596
|
+
return (
|
|
9597
|
+
<div
|
|
9598
|
+
ref={ref}
|
|
9599
|
+
className={cn("flex flex-col bg-card", className)}
|
|
9600
|
+
{...props}
|
|
9601
|
+
>
|
|
9602
|
+
{/* \u2500\u2500\u2500\u2500\u2500 Header \u2500\u2500\u2500\u2500\u2500 */}
|
|
9603
|
+
<PageHeader
|
|
9604
|
+
title={title}
|
|
9605
|
+
actions={headerActions}
|
|
9606
|
+
layout="horizontal"
|
|
9607
|
+
/>
|
|
9608
|
+
|
|
9609
|
+
{/* \u2500\u2500\u2500\u2500\u2500 Plan Selection Area \u2500\u2500\u2500\u2500\u2500 */}
|
|
9610
|
+
<div className="flex flex-col gap-6 px-6 py-6">
|
|
9611
|
+
{/* Tabs + billing toggle */}
|
|
9612
|
+
{tabs.length > 0 && (
|
|
9613
|
+
<PricingToggle
|
|
9614
|
+
tabs={tabs}
|
|
9615
|
+
activeTab={currentTab}
|
|
9616
|
+
onTabChange={handleTabChange}
|
|
9617
|
+
showBillingToggle={showBillingToggle}
|
|
9618
|
+
billingPeriod={currentBilling}
|
|
9619
|
+
onBillingPeriodChange={handleBillingChange}
|
|
9620
|
+
/>
|
|
9621
|
+
)}
|
|
9622
|
+
|
|
9623
|
+
{/* Plan cards grid */}
|
|
9624
|
+
{planCards.length > 0 && (
|
|
9625
|
+
<div
|
|
9626
|
+
className={cn(
|
|
9627
|
+
"grid gap-6 justify-center",
|
|
9628
|
+
planCards.length <= 2
|
|
9629
|
+
? "grid-cols-1 md:grid-cols-2 max-w-[960px] mx-auto"
|
|
9630
|
+
: "grid-cols-1 md:grid-cols-2 lg:grid-cols-3"
|
|
9631
|
+
)}
|
|
9632
|
+
>
|
|
9633
|
+
{planCards.map((cardProps, index) => (
|
|
9634
|
+
<PricingCard key={index} {...cardProps} />
|
|
9635
|
+
))}
|
|
9636
|
+
</div>
|
|
9637
|
+
)}
|
|
9638
|
+
</div>
|
|
9639
|
+
|
|
9640
|
+
{/* \u2500\u2500\u2500\u2500\u2500 Power-ups Section \u2500\u2500\u2500\u2500\u2500 */}
|
|
9641
|
+
{hasPowerUps && (
|
|
9642
|
+
<div className="bg-semantic-bg-ui px-6 py-[60px]">
|
|
9643
|
+
<div className="flex flex-col gap-4">
|
|
9644
|
+
{/* Section header */}
|
|
9645
|
+
<div className="flex items-center justify-between">
|
|
9646
|
+
<h2 className="text-lg font-semibold text-semantic-text-primary m-0">
|
|
9647
|
+
{powerUpsTitle}
|
|
9648
|
+
</h2>
|
|
9649
|
+
{onFeatureComparisonClick && (
|
|
9650
|
+
<Button
|
|
9651
|
+
variant="link"
|
|
9652
|
+
className="text-semantic-text-link p-0 h-auto min-w-0 gap-1"
|
|
9653
|
+
onClick={onFeatureComparisonClick}
|
|
9654
|
+
>
|
|
9655
|
+
{featureComparisonText}
|
|
9656
|
+
<ExternalLink className="size-3.5" />
|
|
9657
|
+
</Button>
|
|
9658
|
+
)}
|
|
9659
|
+
</div>
|
|
9660
|
+
|
|
9661
|
+
{/* Power-up cards */}
|
|
9662
|
+
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
|
9663
|
+
{powerUpCards.map((cardProps, index) => (
|
|
9664
|
+
<PowerUpCard key={index} {...cardProps} />
|
|
9665
|
+
))}
|
|
9666
|
+
</div>
|
|
9667
|
+
</div>
|
|
9668
|
+
</div>
|
|
9669
|
+
)}
|
|
9670
|
+
|
|
9671
|
+
{/* \u2500\u2500\u2500\u2500\u2500 Let Us Drive Section \u2500\u2500\u2500\u2500\u2500 */}
|
|
9672
|
+
{hasLetUsDrive && (
|
|
9673
|
+
<div className="bg-card px-6 py-[60px]">
|
|
9674
|
+
<div className="flex flex-col gap-4">
|
|
9675
|
+
{/* Section header */}
|
|
9676
|
+
<h2 className="text-lg font-semibold text-semantic-text-primary m-0">
|
|
9677
|
+
{letUsDriveTitle}
|
|
9678
|
+
</h2>
|
|
9679
|
+
|
|
9680
|
+
{/* Service cards */}
|
|
9681
|
+
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
|
9682
|
+
{letUsDriveCards.map((cardProps, index) => (
|
|
9683
|
+
<LetUsDriveCard key={index} {...cardProps} />
|
|
9684
|
+
))}
|
|
9685
|
+
</div>
|
|
9686
|
+
</div>
|
|
9687
|
+
</div>
|
|
9688
|
+
)}
|
|
9689
|
+
</div>
|
|
9690
|
+
);
|
|
9691
|
+
}
|
|
9692
|
+
);
|
|
9693
|
+
|
|
9694
|
+
PricingPage.displayName = "PricingPage";
|
|
9695
|
+
|
|
9696
|
+
export { PricingPage };
|
|
9697
|
+
`, prefix)
|
|
9698
|
+
},
|
|
9699
|
+
{
|
|
9700
|
+
name: "types.ts",
|
|
9701
|
+
content: prefixTailwindClasses(`import * as React from "react";
|
|
9702
|
+
import type { PricingCardProps } from "../pricing-card/types";
|
|
9703
|
+
import type { PowerUpCardProps } from "../power-up-card/types";
|
|
9704
|
+
import type { LetUsDriveCardProps } from "../let-us-drive-card/types";
|
|
9705
|
+
import type { PricingToggleTab } from "../pricing-toggle/types";
|
|
9706
|
+
|
|
9707
|
+
export type { PricingToggleTab };
|
|
9708
|
+
|
|
9709
|
+
/**
|
|
9710
|
+
* Props for the PricingPage component.
|
|
9711
|
+
*
|
|
9712
|
+
* PricingPage is a layout compositor that orchestrates PricingToggle,
|
|
9713
|
+
* PricingCard, PowerUpCard, LetUsDriveCard, and PageHeader into
|
|
9714
|
+
* the full plan selection page.
|
|
9715
|
+
*/
|
|
9716
|
+
export interface PricingPageProps
|
|
9717
|
+
extends React.HTMLAttributes<HTMLDivElement> {
|
|
9718
|
+
/* \u2500\u2500\u2500\u2500\u2500 Header \u2500\u2500\u2500\u2500\u2500 */
|
|
9719
|
+
|
|
9720
|
+
/** Page title (default: "Select business plan") */
|
|
9721
|
+
title?: string;
|
|
9722
|
+
/** Actions rendered on the right side of the header (e.g., number-type dropdown) */
|
|
9723
|
+
headerActions?: React.ReactNode;
|
|
9724
|
+
|
|
9725
|
+
/* \u2500\u2500\u2500\u2500\u2500 Tabs & Billing \u2500\u2500\u2500\u2500\u2500 */
|
|
9726
|
+
|
|
9727
|
+
/** Plan type tabs shown in the pill selector */
|
|
9728
|
+
tabs?: PricingToggleTab[];
|
|
9729
|
+
/** Currently active tab value (controlled). Falls back to first tab when unset. */
|
|
9730
|
+
activeTab?: string;
|
|
9731
|
+
/** Callback when the active tab changes */
|
|
9732
|
+
onTabChange?: (value: string) => void;
|
|
9733
|
+
/** Whether to show the monthly/yearly billing toggle */
|
|
9734
|
+
showBillingToggle?: boolean;
|
|
9735
|
+
/** Current billing period (controlled) */
|
|
9736
|
+
billingPeriod?: "monthly" | "yearly";
|
|
9737
|
+
/** Callback when the billing period changes */
|
|
9738
|
+
onBillingPeriodChange?: (period: "monthly" | "yearly") => void;
|
|
9739
|
+
|
|
9740
|
+
/* \u2500\u2500\u2500\u2500\u2500 Plan Cards \u2500\u2500\u2500\u2500\u2500 */
|
|
9741
|
+
|
|
9742
|
+
/** Array of plan card props to render in the main pricing grid */
|
|
9743
|
+
planCards?: PricingCardProps[];
|
|
9744
|
+
|
|
9745
|
+
/* \u2500\u2500\u2500\u2500\u2500 Power-ups Section \u2500\u2500\u2500\u2500\u2500 */
|
|
9746
|
+
|
|
9747
|
+
/** Array of power-up card props */
|
|
9748
|
+
powerUpCards?: PowerUpCardProps[];
|
|
9749
|
+
/** Power-ups section heading (default: "Power-ups and charges") */
|
|
9750
|
+
powerUpsTitle?: string;
|
|
9751
|
+
/** Feature comparison link text (default: "See full feature comparison") */
|
|
9752
|
+
featureComparisonText?: string;
|
|
9753
|
+
/** Callback when the feature comparison link is clicked */
|
|
9754
|
+
onFeatureComparisonClick?: () => void;
|
|
9755
|
+
|
|
9756
|
+
/* \u2500\u2500\u2500\u2500\u2500 Let Us Drive Section \u2500\u2500\u2500\u2500\u2500 */
|
|
9757
|
+
|
|
9758
|
+
/** Array of let-us-drive card props */
|
|
9759
|
+
letUsDriveCards?: LetUsDriveCardProps[];
|
|
9760
|
+
/** Let-us-drive section heading (default: "Let us drive \u2014 Full-service management") */
|
|
9761
|
+
letUsDriveTitle?: string;
|
|
9762
|
+
}
|
|
9763
|
+
`, prefix)
|
|
9764
|
+
},
|
|
9765
|
+
{
|
|
9766
|
+
name: "index.ts",
|
|
9767
|
+
content: prefixTailwindClasses(`export { PricingPage } from "./pricing-page";
|
|
9768
|
+
export type { PricingPageProps, PricingToggleTab } from "./types";
|
|
9769
|
+
`, prefix)
|
|
9770
|
+
}
|
|
9771
|
+
]
|
|
9772
|
+
},
|
|
9773
|
+
"pricing-toggle": {
|
|
9774
|
+
name: "pricing-toggle",
|
|
9775
|
+
description: "A plan type tab selector with billing period toggle for pricing pages. Pill-shaped tabs switch plan categories, and an optional switch toggles between monthly/yearly billing.",
|
|
9776
|
+
category: "custom",
|
|
9777
|
+
dependencies: [
|
|
9778
|
+
"clsx",
|
|
9779
|
+
"tailwind-merge",
|
|
9780
|
+
"@radix-ui/react-switch@^1.2.6"
|
|
9781
|
+
],
|
|
9782
|
+
internalDependencies: [
|
|
9783
|
+
"switch"
|
|
9784
|
+
],
|
|
9785
|
+
isMultiFile: true,
|
|
9786
|
+
directory: "pricing-toggle",
|
|
9787
|
+
mainFile: "pricing-toggle.tsx",
|
|
9788
|
+
files: [
|
|
9789
|
+
{
|
|
9790
|
+
name: "pricing-toggle.tsx",
|
|
9791
|
+
content: prefixTailwindClasses(`import * as React from "react";
|
|
9792
|
+
import { cn } from "../../../lib/utils";
|
|
9793
|
+
import { Switch } from "../switch";
|
|
9794
|
+
import type { PricingToggleProps } from "./types";
|
|
9795
|
+
|
|
9796
|
+
/**
|
|
9797
|
+
* PricingToggle provides a plan type tab selector with an optional
|
|
9798
|
+
* billing period toggle. The pill-shaped tabs switch between plan
|
|
9799
|
+
* categories (e.g. "Team-Led Plans" vs "Go-AI First"), and the
|
|
9800
|
+
* billing toggle switches between monthly/yearly pricing.
|
|
9801
|
+
*
|
|
9802
|
+
* @example
|
|
9803
|
+
* \`\`\`tsx
|
|
9804
|
+
* <PricingToggle
|
|
9805
|
+
* tabs={[
|
|
9806
|
+
* { label: "Team-Led Plans", value: "team" },
|
|
9807
|
+
* { label: "Go-AI First", value: "ai" },
|
|
9808
|
+
* ]}
|
|
9809
|
+
* activeTab="team"
|
|
9810
|
+
* onTabChange={(value) => setActiveTab(value)}
|
|
9811
|
+
* showBillingToggle
|
|
9812
|
+
* billingPeriod="monthly"
|
|
9813
|
+
* onBillingPeriodChange={(period) => setBillingPeriod(period)}
|
|
9814
|
+
* />
|
|
9815
|
+
* \`\`\`
|
|
9816
|
+
*/
|
|
9817
|
+
const PricingToggle = React.forwardRef<HTMLDivElement, PricingToggleProps>(
|
|
9818
|
+
(
|
|
9819
|
+
{
|
|
9820
|
+
tabs,
|
|
9821
|
+
activeTab,
|
|
9822
|
+
onTabChange,
|
|
9823
|
+
showBillingToggle = false,
|
|
9824
|
+
billingPeriod = "monthly",
|
|
9825
|
+
onBillingPeriodChange,
|
|
9826
|
+
monthlyLabel = "Monthly",
|
|
9827
|
+
yearlyLabel = "Yearly (Save 20%)",
|
|
9828
|
+
className,
|
|
9829
|
+
...props
|
|
9830
|
+
},
|
|
9831
|
+
ref
|
|
9832
|
+
) => {
|
|
9833
|
+
const isYearly = billingPeriod === "yearly";
|
|
9834
|
+
|
|
9835
|
+
return (
|
|
9836
|
+
<div
|
|
9837
|
+
ref={ref}
|
|
9838
|
+
className={cn("flex flex-col items-center gap-4", className)}
|
|
9839
|
+
{...props}
|
|
9840
|
+
>
|
|
9841
|
+
{/* Plan type tabs */}
|
|
9842
|
+
<div className="inline-flex items-start gap-1 rounded-full bg-semantic-bg-ui p-1">
|
|
9843
|
+
{tabs.map((tab) => {
|
|
9844
|
+
const isActive = tab.value === activeTab;
|
|
9845
|
+
return (
|
|
9846
|
+
<button
|
|
9847
|
+
key={tab.value}
|
|
9848
|
+
type="button"
|
|
9849
|
+
role="tab"
|
|
9850
|
+
aria-selected={isActive}
|
|
9851
|
+
className={cn(
|
|
9852
|
+
"h-10 shrink-0 rounded-full px-4 py-1 text-base transition-colors",
|
|
9853
|
+
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-semantic-brand focus-visible:ring-offset-2",
|
|
9854
|
+
isActive
|
|
9855
|
+
? "bg-semantic-brand font-semibold text-white shadow-sm"
|
|
9856
|
+
: "font-normal text-semantic-text-primary"
|
|
9857
|
+
)}
|
|
9858
|
+
onClick={() => onTabChange(tab.value)}
|
|
9859
|
+
>
|
|
9860
|
+
{tab.label}
|
|
9861
|
+
</button>
|
|
9862
|
+
);
|
|
9863
|
+
})}
|
|
9864
|
+
</div>
|
|
9865
|
+
|
|
9866
|
+
{/* Billing period toggle */}
|
|
9867
|
+
{showBillingToggle && (
|
|
9868
|
+
<div className="flex items-center gap-4">
|
|
9869
|
+
<span
|
|
9870
|
+
className={cn(
|
|
9871
|
+
"text-sm font-semibold tracking-[0.014px]",
|
|
9872
|
+
!isYearly
|
|
9873
|
+
? "text-semantic-text-secondary"
|
|
9874
|
+
: "text-semantic-text-muted"
|
|
9875
|
+
)}
|
|
9876
|
+
>
|
|
9877
|
+
{monthlyLabel}
|
|
9878
|
+
</span>
|
|
9879
|
+
<Switch
|
|
9880
|
+
size="sm"
|
|
9881
|
+
checked={isYearly}
|
|
9882
|
+
onCheckedChange={(checked) =>
|
|
9883
|
+
onBillingPeriodChange?.(checked ? "yearly" : "monthly")
|
|
9884
|
+
}
|
|
9885
|
+
/>
|
|
9886
|
+
<span
|
|
9887
|
+
className={cn(
|
|
9888
|
+
"text-sm font-semibold tracking-[0.014px]",
|
|
9889
|
+
isYearly
|
|
9890
|
+
? "text-semantic-text-secondary"
|
|
9891
|
+
: "text-semantic-text-muted"
|
|
9892
|
+
)}
|
|
9893
|
+
>
|
|
9894
|
+
{yearlyLabel}
|
|
9895
|
+
</span>
|
|
9896
|
+
</div>
|
|
9897
|
+
)}
|
|
9898
|
+
</div>
|
|
9899
|
+
);
|
|
9900
|
+
}
|
|
9901
|
+
);
|
|
9902
|
+
|
|
9903
|
+
PricingToggle.displayName = "PricingToggle";
|
|
9904
|
+
|
|
9905
|
+
export { PricingToggle };
|
|
9906
|
+
`, prefix)
|
|
9907
|
+
},
|
|
9908
|
+
{
|
|
9909
|
+
name: "types.ts",
|
|
9910
|
+
content: prefixTailwindClasses(`/** A single tab option in the plan tab selector */
|
|
9911
|
+
export interface PricingToggleTab {
|
|
9912
|
+
/** Display label for the tab */
|
|
9913
|
+
label: string;
|
|
9914
|
+
/** Unique value identifier for the tab */
|
|
9915
|
+
value: string;
|
|
9916
|
+
}
|
|
9917
|
+
|
|
9918
|
+
export interface PricingToggleProps
|
|
9919
|
+
extends React.HTMLAttributes<HTMLDivElement> {
|
|
9920
|
+
/** Array of tab options for the plan type selector */
|
|
9921
|
+
tabs: PricingToggleTab[];
|
|
9922
|
+
/** Currently active tab value (controlled) */
|
|
9923
|
+
activeTab: string;
|
|
9924
|
+
/** Callback when the active tab changes */
|
|
9925
|
+
onTabChange: (value: string) => void;
|
|
9926
|
+
/** Whether to show the billing period toggle below the tabs */
|
|
9927
|
+
showBillingToggle?: boolean;
|
|
9928
|
+
/** Current billing period \u2014 "monthly" or "yearly" (controlled) */
|
|
9929
|
+
billingPeriod?: "monthly" | "yearly";
|
|
9930
|
+
/** Callback when the billing period changes */
|
|
9931
|
+
onBillingPeriodChange?: (period: "monthly" | "yearly") => void;
|
|
9932
|
+
/** Left label for the billing toggle (default: "Monthly") */
|
|
9933
|
+
monthlyLabel?: string;
|
|
9934
|
+
/** Right label for the billing toggle (default: "Yearly (Save 20%)") */
|
|
9935
|
+
yearlyLabel?: string;
|
|
9936
|
+
}
|
|
9937
|
+
`, prefix)
|
|
9938
|
+
},
|
|
9939
|
+
{
|
|
9940
|
+
name: "index.ts",
|
|
9941
|
+
content: prefixTailwindClasses(`export { PricingToggle } from "./pricing-toggle";
|
|
9942
|
+
export type { PricingToggleProps, PricingToggleTab } from "./types";
|
|
8721
9943
|
`, prefix)
|
|
8722
9944
|
}
|
|
8723
9945
|
]
|