{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "pricing-plans",
  "title": "Pricing Plans",
  "author": "@fma-ds/ds",
  "description": "Choose one of a few fixed plans, then confirm. For a plan built from quantities and add-ons, use `plan-builder`.\n\n- **`PricingPlans`**: lays out the options and an optional `summary` (an `OrderSummary` with the chosen plan and a \"Continue\" action: a sticky card on large screens, a bar fixed to the bottom on smaller ones).\n- **`PricingPeriod`**: the billing period as a segmented toggle that always has one selected (`value`/`onChange`; `PricingPeriodOption` with an `id`, and a `Badge` inside for the saving).\n- **`PricingPlanGroup`**: a radio group of `PricingPlan` cards, stacked in one list on small screens and side by side in equal columns once its own width fits them.\n- **`PricingPlan`**: `name`, `badge` (\"Most popular\"), `description`, `price` and `period` formatted by the app, `priceNote` (the equivalent, \"R$ 718,80 per year\") and `isFeatured` for a brand glow and a `brand` badge (the border is left to the selected plan). The radio is named by the plan and described by its price.\n- **`PricingPlanFeature`**: what the plan has; `isIncluded={false}` shows a dash, muted text and \"Not included\" read before it.\n\nBuilt-in text is in English; translate it with `i18n.labels`.",
  "dependencies": [
    "cn@^0.5.0",
    "lucide-react@^1.61.0",
    "react-aria-components@^1.22.1"
  ],
  "registryDependencies": [
    "@fma-ds/badge",
    "@fma-ds/radio-group",
    "@fma-ds/toggle-group",
    "@fma-ds/brand"
  ],
  "files": [
    {
      "path": "src/blocks/pricing-plans/pricing-plans.tsx",
      "content": "\"use client\";\n\nimport { cn } from \"cn\";\nimport { CheckIcon, MinusIcon } from \"lucide-react\";\nimport * as React from \"react\";\nimport type { RadioGroupProps, RadioProps, ToggleButtonGroupProps } from \"react-aria-components\";\nimport { Badge } from \"@/core/badge/badge\";\nimport { RadioCard, RadioGroup } from \"@/core/radio-group/radio-group\";\nimport { ToggleGroup, ToggleGroupItem } from \"@/core/toggle-group/toggle-group\";\nimport { brandGlow } from \"@/lib/brand\";\n\ntype PricingPlansLabels = {\n  /** Read before an included feature. */\n  included: string;\n  /** Read before a feature the plan doesn't have. */\n  notIncluded: string;\n};\n\ntype PricingPlansI18n = { labels?: Partial<PricingPlansLabels> };\n\nconst defaultPricingPlansLabels: PricingPlansLabels = {\n  included: \"Included\",\n  notIncluded: \"Not included\",\n};\n\nconst PricingPlansContext = React.createContext<PricingPlansLabels>(defaultPricingPlansLabels);\n\ntype PricingPlansProps = React.ComponentProps<\"div\"> & {\n  /** The side column: an `OrderSummary` with the chosen plan and the action (\"Continue\"). */\n  summary?: React.ReactNode;\n  i18n?: PricingPlansI18n;\n};\n\n/**\n * Choose one of a few fixed plans, then confirm: the period (`PricingPeriod`)\n * and the plans (`PricingPlanGroup`) on the left, the `summary` on the right,\n * which sticks on large screens and becomes the `OrderSummary` bar fixed to\n * the bottom on smaller ones (the bottom padding leaves room for it). Prices,\n * periods and features come from the app.\n */\nfunction PricingPlans({ summary, i18n, className, children, ...props }: PricingPlansProps) {\n  const included = i18n?.labels?.included ?? defaultPricingPlansLabels.included;\n  const notIncluded = i18n?.labels?.notIncluded ?? defaultPricingPlansLabels.notIncluded;\n  const labels = React.useMemo(() => ({ included, notIncluded }), [included, notIncluded]);\n\n  return (\n    <PricingPlansContext.Provider value={labels}>\n      <div\n        data-slot=\"pricing-plans\"\n        className={cn(\n          \"grid gap-6\",\n          summary && \"pb-28 lg:grid-cols-[minmax(0,1fr)_22rem] lg:items-start lg:pb-0\",\n          className,\n        )}\n        {...props}\n      >\n        <div data-slot=\"pricing-plans-options\" className=\"flex min-w-0 flex-col gap-4\">\n          {children}\n        </div>\n        {summary && <div data-slot=\"pricing-plans-summary\">{summary}</div>}\n      </div>\n    </PricingPlansContext.Provider>\n  );\n}\n\ntype PricingPeriodProps = Omit<\n  ToggleButtonGroupProps,\n  \"selectionMode\" | \"selectedKeys\" | \"defaultSelectedKeys\" | \"onSelectionChange\" | \"children\"\n> & {\n  /** The selected period's `id`. One is always selected. */\n  value: string;\n  onChange: (value: string) => void;\n  /** `PricingPeriodOption`s. */\n  children: React.ReactNode;\n};\n\n/** The billing period (monthly, annual): a segmented toggle where one is always selected. */\nfunction PricingPeriod({ value, onChange, className, children, ...props }: PricingPeriodProps) {\n  return (\n    <ToggleGroup\n      data-slot=\"pricing-period\"\n      selectionMode=\"single\"\n      disallowEmptySelection\n      selectedKeys={[value]}\n      onSelectionChange={(keys) => {\n        const [next] = keys;\n        if (next != null) onChange(String(next));\n      }}\n      variant=\"outline\"\n      spacing={0}\n      className={className}\n      {...props}\n    >\n      {children}\n    </ToggleGroup>\n  );\n}\n\n/** One period; `id` is its value. A `Badge` inside can show the saving (\"2 months free\"). */\nfunction PricingPeriodOption({\n  className,\n  ...props\n}: React.ComponentProps<typeof ToggleGroupItem> & { id: string }) {\n  return (\n    <ToggleGroupItem\n      data-slot=\"pricing-period-option\"\n      className={cn(\"px-3\", className)}\n      {...props}\n    />\n  );\n}\n\n/**\n * The plans as a radio group (`value`/`onChange`, named by `aria-label` or a\n * `Label`). One list on small screens; side by side, in equal columns, once\n * its own width fits them.\n */\nfunction PricingPlanGroup({\n  className,\n  children,\n  ...props\n}: Omit<RadioGroupProps, \"className\"> & { className?: string }) {\n  return (\n    <RadioGroup data-slot=\"pricing-plan-group\" className={cn(\"@container\", className)} {...props}>\n      {(renderProps) => (\n        <div className=\"grid gap-4 @2xl:auto-cols-fr @2xl:grid-flow-col\">\n          {typeof children === \"function\" ? children(renderProps) : children}\n        </div>\n      )}\n    </RadioGroup>\n  );\n}\n\ntype PricingPlanProps = Omit<RadioProps, \"children\" | \"aria-labelledby\" | \"aria-describedby\"> & {\n  name: React.ReactNode;\n  description?: React.ReactNode;\n  /** The price, formatted by the app (\"R$ 59,90\"). */\n  price: React.ReactNode;\n  /** After the price (\"/month\"). */\n  period?: React.ReactNode;\n  /** Under the price: the equivalent (\"R$ 718,80 per year\") or a condition. */\n  priceNote?: React.ReactNode;\n  /** A `Badge` beside the name (\"Most popular\"). */\n  badge?: React.ReactNode;\n  /**\n   * Highlights the plan with a brand glow and a `brand` badge (pass `badge`, so it\n   * isn't color alone). Not a border: the border marks the selected plan.\n   */\n  isFeatured?: boolean;\n  /** `PricingPlanFeature`s. */\n  children?: React.ReactNode;\n};\n\n/**\n * One plan as a card the user selects: name, badge, description, price and\n * the features. The radio is named by the plan's name and described by its\n * price and badge; the features stay readable in the card.\n */\nfunction PricingPlan({\n  name,\n  description,\n  price,\n  period,\n  priceNote,\n  badge,\n  isFeatured = false,\n  className,\n  children,\n  ...props\n}: PricingPlanProps) {\n  const id = React.useId();\n  const nameId = `${id}-name`;\n  const priceId = `${id}-price`;\n  const badgeId = `${id}-badge`;\n\n  return (\n    <RadioCard\n      data-slot=\"pricing-plan\"\n      data-featured={isFeatured || undefined}\n      aria-labelledby={nameId}\n      aria-describedby={badge ? `${priceId} ${badgeId}` : priceId}\n      className={cn(\"h-full\", isFeatured && brandGlow, className)}\n      {...props}\n    >\n      <div className=\"flex min-w-0 flex-1 flex-col gap-4\">\n        <div className=\"flex flex-col gap-1\">\n          <div className=\"flex flex-wrap items-center gap-2\">\n            <span id={nameId} className=\"text-base font-medium\">\n              {name}\n            </span>\n            {badge && (\n              <Badge id={badgeId} variant={isFeatured ? \"brand\" : \"default\"}>\n                {badge}\n              </Badge>\n            )}\n          </div>\n          {description && <span className=\"text-muted-foreground\">{description}</span>}\n        </div>\n        <div id={priceId} className=\"flex flex-col gap-0.5\">\n          <span>\n            <span className=\"text-2xl font-semibold tabular-nums\">{price}</span>\n            {period && <span className=\"text-muted-foreground\"> {period}</span>}\n          </span>\n          {priceNote && <span className=\"text-xs text-muted-foreground\">{priceNote}</span>}\n        </div>\n        {children && (\n          <ul data-slot=\"pricing-plan-features\" className=\"flex flex-col gap-2 border-t pt-4\">\n            {children}\n          </ul>\n        )}\n      </div>\n    </RadioCard>\n  );\n}\n\n/**\n * A feature of a plan: a check when included, a dash and muted text when not.\n * The state is read too (\"Not included: …\"), not only drawn.\n */\nfunction PricingPlanFeature({\n  isIncluded = true,\n  className,\n  children,\n  ...props\n}: React.ComponentProps<\"li\"> & { isIncluded?: boolean }) {\n  const labels = React.useContext(PricingPlansContext);\n  return (\n    <li\n      data-slot=\"pricing-plan-feature\"\n      data-included={isIncluded}\n      className={cn(\"flex items-start gap-2\", !isIncluded && \"text-muted-foreground\", className)}\n      {...props}\n    >\n      {isIncluded ? (\n        <CheckIcon aria-hidden=\"true\" className=\"mt-0.5 size-4 shrink-0 text-primary\" />\n      ) : (\n        <MinusIcon aria-hidden=\"true\" className=\"mt-0.5 size-4 shrink-0\" />\n      )}\n      <span>\n        <span className=\"sr-only\">{isIncluded ? labels.included : labels.notIncluded}: </span>\n        {children}\n      </span>\n    </li>\n  );\n}\n\nexport type { PricingPlanProps, PricingPlansI18n, PricingPlansLabels, PricingPlansProps };\nexport {\n  PricingPeriod,\n  PricingPeriodOption,\n  PricingPlan,\n  PricingPlanFeature,\n  PricingPlanGroup,\n  PricingPlans,\n};\n",
      "type": "registry:component",
      "target": "components/fma-ds/pricing-plans/pricing-plans.tsx"
    }
  ],
  "meta": {
    "status": "experimental",
    "source": "original",
    "license": "MIT",
    "tags": [
      "block",
      "checkout",
      "plan",
      "pricing",
      "subscription"
    ],
    "contentHash": "4cae110cc392"
  },
  "categories": [
    "blocks"
  ],
  "type": "registry:block"
}