@eifi1/ui-kit 0.52.0 → 0.53.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/README.md +47 -54
- package/dist/billing/cancel-subscription.d.ts +5 -14
- package/dist/billing/cancel-subscription.js +1 -3
- package/dist/billing/cancel-subscription.js.map +1 -1
- package/dist/billing/plan-card.d.ts +5 -13
- package/dist/billing/plan-card.js +3 -11
- package/dist/billing/plan-card.js.map +1 -1
- package/dist/billing/plan-switch.d.ts +5 -14
- package/dist/billing/plan-switch.js +1 -3
- package/dist/billing/plan-switch.js.map +1 -1
- package/dist/chart.d.ts +3 -14
- package/dist/chart.js +64 -11
- package/dist/chart.js.map +1 -1
- package/dist/components/date-field.d.ts +1 -1
- package/dist/components/date-field.js.map +1 -1
- package/dist/components/date-picker.d.ts +1 -1
- package/dist/components/date-picker.js.map +1 -1
- package/dist/components/date-range-picker.d.ts +4 -17
- package/dist/components/date-range-picker.js +2 -2
- package/dist/components/date-range-picker.js.map +1 -1
- package/dist/components/nav-pills.d.ts +1 -1
- package/dist/components/nav-pills.js.map +1 -1
- package/dist/components/series-chart-axes.d.ts +1 -1
- package/dist/components/series-chart-axes.js.map +1 -1
- package/dist/components/series-chart.d.ts +64 -19
- package/dist/components/series-chart.js +2 -1
- package/dist/components/series-chart.js.map +1 -1
- package/dist/components/treemap-labels.d.ts +6 -1
- package/dist/components/treemap-labels.js +2 -2
- package/dist/components/treemap-labels.js.map +1 -1
- package/dist/components/treemap.d.ts +29 -4
- package/dist/components/treemap.js +22 -9
- package/dist/components/treemap.js.map +1 -1
- package/dist/data-table.d.ts +1 -6
- package/dist/data-table.js +1 -9
- package/dist/data-table.js.map +1 -1
- package/dist/demo/demo-countdown.d.ts +0 -5
- package/dist/demo/demo-countdown.js +0 -4
- package/dist/demo/demo-countdown.js.map +1 -1
- package/dist/eslint/index.js +1 -1
- package/dist/feedback/feedback-mutation.d.ts +11 -23
- package/dist/feedback/feedback-mutation.js +1 -9
- package/dist/feedback/feedback-mutation.js.map +1 -1
- package/dist/feedback/feedback-public-list.d.ts +1 -6
- package/dist/feedback/feedback-public-list.js +1 -2
- package/dist/feedback/feedback-public-list.js.map +1 -1
- package/dist/feedback/feedback-row-detail-labels.js +0 -8
- package/dist/feedback/feedback-row-detail-labels.js.map +1 -1
- package/dist/feedback/feedback-row-detail.d.ts +3 -50
- package/dist/feedback/feedback-row-detail.js +0 -8
- package/dist/feedback/feedback-row-detail.js.map +1 -1
- package/dist/feedback/feedback-status-undo.d.ts +3 -19
- package/dist/feedback/feedback-status-undo.js.map +1 -1
- package/dist/feedback/feedback-thread.d.ts +0 -9
- package/dist/feedback/feedback-thread.js.map +1 -1
- package/dist/feedback.d.ts +6 -40
- package/dist/feedback.js +88 -46
- package/dist/feedback.js.map +1 -1
- package/dist/i18n/defaults.js +0 -4
- package/dist/i18n/defaults.js.map +1 -1
- package/dist/i18n/german.js +2 -19
- package/dist/i18n/german.js.map +1 -1
- package/dist/i18n/kit-labels.d.ts +5 -14
- package/dist/i18n/kit-labels.js.map +1 -1
- package/dist/i18n/locales/en.js +2 -30
- package/dist/i18n/locales/en.js.map +3 -3
- package/dist/i18n/locales/es.js +2 -19
- package/dist/i18n/locales/es.js.map +1 -1
- package/dist/i18n/locales/fr.js +2 -19
- package/dist/i18n/locales/fr.js.map +1 -1
- package/dist/i18n/locales/hu.js +2 -19
- package/dist/i18n/locales/hu.js.map +1 -1
- package/dist/i18n/locales/it.js +2 -20
- package/dist/i18n/locales/it.js.map +1 -1
- package/dist/i18n/locales/zh.js +2 -19
- package/dist/i18n/locales/zh.js.map +1 -1
- package/dist/i18n/review.js +9 -4
- package/dist/i18n/review.js.map +1 -1
- package/dist/i18n/shared-labels.js +0 -1
- package/dist/i18n/shared-labels.js.map +1 -1
- package/dist/index.d.ts +15 -64
- package/dist/index.js +22 -102
- package/dist/index.js.map +1 -1
- package/dist/rhf/fields.d.ts +2 -2
- package/dist/rhf/fields.js.map +1 -1
- package/dist/search/search-index.d.ts +0 -6
- package/dist/search/search-index.js +0 -4
- package/dist/search/search-index.js.map +1 -1
- package/dist/search.d.ts +1 -8
- package/dist/search.js +2 -7
- package/dist/search.js.map +1 -1
- package/dist/shell/account-menu-language.d.ts +7 -21
- package/dist/shell/account-menu-language.js +1 -3
- package/dist/shell/account-menu-language.js.map +1 -1
- package/dist/shell/account-menu-text-size.d.ts +9 -23
- package/dist/shell/account-menu-text-size.js +2 -3
- package/dist/shell/account-menu-text-size.js.map +1 -1
- package/dist/shell/app-shell.d.ts +0 -10
- package/dist/shell/app-shell.js +1 -7
- package/dist/shell/app-shell.js.map +1 -1
- package/dist/shell/company-switcher.d.ts +1 -1
- package/dist/shell/company-switcher.js.map +1 -1
- package/dist/shell/dev-menu.d.ts +1 -1
- package/dist/shell/dev-menu.js.map +1 -1
- package/dist/shell/top-bar-parts-labels.d.ts +1 -1
- package/dist/shell/top-bar-parts-labels.js.map +1 -1
- package/dist/shell.d.ts +7 -29
- package/dist/shell.js +12 -42
- package/dist/shell.js.map +1 -1
- package/dist/sync.d.ts +2 -6
- package/dist/sync.js +2 -15
- package/dist/sync.js.map +1 -1
- package/dist/theme/chart-palette.d.ts +0 -5
- package/dist/theme/chart-palette.js +0 -2
- package/dist/theme/chart-palette.js.map +1 -1
- package/package.json +1 -1
- package/src/billing/cancel-subscription.tsx +5 -16
- package/src/billing/plan-card.tsx +7 -24
- package/src/billing/plan-switch.tsx +5 -16
- package/src/chart.ts +62 -13
- package/src/components/date-field.tsx +1 -1
- package/src/components/date-picker.tsx +0 -1
- package/src/components/date-range-picker.tsx +6 -22
- package/src/components/nav-pills.tsx +1 -1
- package/src/components/series-chart-axes.ts +6 -1
- package/src/components/series-chart.tsx +78 -21
- package/src/components/treemap-labels.ts +11 -3
- package/src/components/treemap.tsx +65 -12
- package/src/data-table.ts +1 -11
- package/src/demo/demo-countdown.ts +3 -8
- package/src/feedback/feedback-mutation.ts +12 -33
- package/src/feedback/feedback-public-list.tsx +1 -7
- package/src/feedback/feedback-row-detail-labels.ts +0 -8
- package/src/feedback/feedback-row-detail.tsx +8 -58
- package/src/feedback/feedback-status-undo.ts +4 -18
- package/src/feedback/feedback-thread.tsx +0 -10
- package/src/feedback.ts +89 -48
- package/src/i18n/defaults.ts +0 -4
- package/src/i18n/german.ts +2 -19
- package/src/i18n/kit-labels.tsx +5 -14
- package/src/i18n/locales/es.ts +2 -19
- package/src/i18n/locales/fr.ts +2 -19
- package/src/i18n/locales/hu.ts +2 -19
- package/src/i18n/locales/it.ts +2 -20
- package/src/i18n/locales/zh.ts +2 -19
- package/src/i18n/review.ts +9 -4
- package/src/i18n/shared-labels.ts +0 -1
- package/src/index.ts +30 -112
- package/src/rhf/fields.tsx +2 -2
- package/src/search/search-index.ts +0 -15
- package/src/search.ts +8 -8
- package/src/shell/account-menu-language.tsx +7 -27
- package/src/shell/account-menu-text-size.tsx +13 -32
- package/src/shell/app-shell.tsx +0 -13
- package/src/shell/company-switcher.tsx +1 -1
- package/src/shell/dev-menu.tsx +1 -1
- package/src/shell/top-bar-parts-labels.ts +1 -1
- package/src/shell.ts +17 -44
- package/src/sync.ts +2 -15
- package/src/theme/chart-palette.ts +0 -2
- package/tokens.css +4 -4
- package/dist/billing/dialog-close.d.ts +0 -29
- package/dist/billing/dialog-close.js +0 -8
- package/dist/billing/dialog-close.js.map +0 -1
- package/dist/components/field-sync-labels.d.ts +0 -3
- package/dist/components/field-sync-labels.js +0 -12
- package/dist/components/field-sync-labels.js.map +0 -1
- package/dist/components/field-sync.d.ts +0 -186
- package/dist/components/field-sync.js +0 -269
- package/dist/components/field-sync.js.map +0 -1
- package/dist/components/section-nav.d.ts +0 -44
- package/dist/components/section-nav.js +0 -33
- package/dist/components/section-nav.js.map +0 -1
- package/dist/components/sign-chip-labels.d.ts +0 -3
- package/dist/components/sign-chip-labels.js +0 -11
- package/dist/components/sign-chip-labels.js.map +0 -1
- package/dist/components/sign-chip.d.ts +0 -77
- package/dist/components/sign-chip.js +0 -47
- package/dist/components/sign-chip.js.map +0 -1
- package/dist/lib/isin.d.ts +0 -38
- package/dist/lib/isin.js +0 -36
- package/dist/lib/isin.js.map +0 -1
- package/dist/shell/role-switcher.d.ts +0 -28
- package/dist/shell/role-switcher.js +0 -31
- package/dist/shell/role-switcher.js.map +0 -1
- package/src/billing/dialog-close.ts +0 -34
- package/src/components/field-sync-labels.ts +0 -14
- package/src/components/field-sync.tsx +0 -504
- package/src/components/section-nav.tsx +0 -72
- package/src/components/sign-chip-labels.ts +0 -13
- package/src/components/sign-chip.tsx +0 -133
- package/src/lib/isin.ts +0 -72
- package/src/shell/role-switcher.tsx +0 -53
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/billing/plan-card.tsx"],"sourcesContent":["import { useId, useState } from \"react\";\nimport type { ReactNode } from \"react\";\nimport { Check, Gauge } from \"lucide-react\";\n\nimport { DEFAULT_COMMON_LABELS, useKitLabels, useKitLocale } from \"../i18n/kit-labels\";\nimport type { LabelOverride } from \"../i18n/kit-labels\";\nimport { formatNumber } from \"../lib/format\";\nimport { cn } from \"../lib/cn\";\nimport { ChoiceCard } from \"../components/choice-card\";\nimport type { ChoiceCardProps } from \"../components/choice-card\";\nimport { Chip } from \"../components/chip\";\nimport { ToggleGroup } from \"../components/toggle-group\";\nimport { Button } from \"../components/button\";\nimport { DialogFrame } from \"../components/dialog-frame\";\nimport { useBillingLabels } from \"./billing-labels\";\nimport type { BillingLabels } from \"./billing-labels\";\nimport { dialogClose } from \"./dialog-close\";\nimport type { WithDialogClose } from \"./dialog-close\";\nimport { BILLING_INTERVALS, billingCurrencyFor, formatPlanPrice, planPrice } from \"./plan-price\";\nimport type { BillingCurrency, BillingInterval, PlanPrices } from \"./plan-price\";\n\n/**\n * One plan of the app's catalogue (docs/billing-harmonization.md §3.1) as a page shows it:\n * server-kit's `PlanSpec` from `GET /billing/plans`, plus the words, which are the app's\n * (display names and feature lines come from its i18n, never from the provider).\n */\nexport interface BillingPlan {\n /** The plan code, lowercase on the wire (§12.16); compared without case here. */\n code: string;\n /** The app's display name, in the reader's language: \"Pro\". */\n name: string;\n /** A line under the price. */\n description?: ReactNode;\n /** Gross prices in minor units, per currency and period (§3.1, §12.17). None at all —\n * or 0 — is a free plan. */\n prices?: PlanPrices | null;\n /** Dimension → limit; `null` is unlimited (§3.1). Shown in the object's order. */\n limits?: Readonly<Record<string, number | null>>;\n /** The app's feature lines. */\n features?: readonly ReactNode[];\n /** Not choosable — a plan being retired, say. */\n disabled?: boolean;\n}\n\n/**\n * How a plan stands to the one the payer pays for now: `current` is it, `upgrade` comes\n * after it in the catalogue's order (`PlanSpec.sort`), `downgrade` before it.\n */\nexport type PlanRelation = \"current\" | \"upgrade\" | \"downgrade\";\n\n/** The plan's place against `current`, by the order of `plans`; `undefined` without a\n * current plan (a trial, an ended plan) or for a code that is not in the list. */\nexport function planRelation(\n plans: readonly Pick<BillingPlan, \"code\">[],\n code: string,\n current: string | null | undefined,\n): PlanRelation | undefined {\n if (!current) return undefined;\n const index = (c: string) => plans.findIndex((p) => p.code.toLowerCase() === c.toLowerCase());\n const at = index(code);\n const now = index(current);\n if (at < 0 || now < 0) return undefined;\n return at === now ? \"current\" : at > now ? \"upgrade\" : \"downgrade\";\n}\n\n/** What both parts need to draw a plan. */\ninterface PlanDisplayProps {\n /** The currency the prices are shown in (§2.6). */\n currency: BillingCurrency;\n /** The period the prices are shown for. */\n interval: BillingInterval;\n /** The app's name of each limit's dimension (\"Budgets\"). A dimension without one is\n * shown by its key. */\n dimensionLabels?: Readonly<Record<string, string>>;\n /** How a limit's figure is written — kastlan's storage is bytes (`formatFileSize`).\n * Default: a number in the kit's locale. */\n formatLimit?: (dimension: string, value: number) => string;\n /** Over the provider's locale. */\n locale?: string;\n /** Prop > `<UiKitProvider labels={{ billing }}>` > English. */\n labels?: LabelOverride<BillingLabels>;\n}\n\nexport interface PlanCardProps\n extends\n Omit<ChoiceCardProps, \"type\" | \"title\" | \"description\" | \"icon\" | \"indeterminate\" | \"value\" | \"labels\">,\n PlanDisplayProps {\n plan: BillingPlan;\n /** See {@link PlanRelation}. `current` draws the \"Current plan\" chip — a word in the\n * card's name, never a colour alone. */\n relation?: PlanRelation;\n /**\n * The provider's own localised price in place of the catalogue's figure (§12.17) — a\n * Merchant of Record's price preview for the buyer's country, tax included. The period\n * and \"VAT included\" stay: the law asks for a gross price, and the card says so.\n */\n pricePreview?: ReactNode;\n}\n\n/** A list from spans (`role=\"list\"`): the card is a `<label>`, which may hold phrasing\n * content only, so a `<ul>` there would be invalid HTML. */\nfunction SpanList({ name, items, icon }: { name: string; items: ReactNode[]; icon: ReactNode }) {\n if (items.length === 0) return null;\n return (\n <span role=\"list\" aria-label={name} className=\"block space-y-1\">\n {items.map((item, i) => (\n <span role=\"listitem\" key={i} className=\"flex items-start gap-1.5 text-secondary\">\n <span aria-hidden className=\"mt-[0.0625rem] flex shrink-0 text-muted [&_svg]:size-3.5\">\n {icon}\n </span>\n <span className=\"min-w-0\">\n {item}\n {/* A space between the items' texts in the flattened description. */}{\" \"}\n </span>\n </span>\n ))}\n </span>\n );\n}\n\n/**\n * One plan as a {@link ChoiceCard} radio (§7): the name (and \"Current plan\"), the GROSS\n * price in the chosen currency and period through `formatMoney` (\"CHF 49 per year, VAT\n * included\", §12.17), the limits (\"Budgets: 3\", \"Seats: Unlimited\") and the app's feature\n * lines. A plan with prices but none for this currency and period is shown, \"Not offered\n * for this billing period\", and cannot be picked.\n *\n * A radio of a set: give the cards one `name`, or use {@link PlanPicker}, which does that\n * and owns the choice. Everything else a `ChoiceCard` takes goes to it — `checked`,\n * `onCheckedChange`, `name`, `className`. Carries `data-plan` and `data-relation`.\n */\nexport function PlanCard({\n // The radio `<input>` (ChoiceCard's): a React 19 ref prop since 0.34 (MOD-4, refactor\n // audit 2026-10), where this was `forwardRef`.\n ref,\n plan,\n relation,\n pricePreview,\n currency,\n interval,\n dimensionLabels,\n formatLimit,\n locale: localeProp,\n labels: labelsProp,\n disabled,\n ...rest\n}: PlanCardProps) {\n const labels = useBillingLabels(labelsProp);\n const common = useKitLabels(\"common\", DEFAULT_COMMON_LABELS);\n const locale = useKitLocale(localeProp);\n const price = planPrice(plan.prices, currency, interval);\n\n const priced = price.kind === \"price\";\n const figure = priced\n ? pricePreview !== undefined && pricePreview !== null\n ? pricePreview\n : formatPlanPrice(price.minor, currency, { locale })\n : price.kind === \"free\"\n ? labels.free\n : labels.notOffered;\n\n const limitLines = Object.entries(plan.limits ?? {}).map(([dimension, value]) => {\n const name = dimensionLabels?.[dimension] ?? dimension;\n const shown =\n value === null ? labels.unlimited : formatLimit ? formatLimit(dimension, value) : formatNumber(value, { locale });\n return common.fieldValue(name, shown);\n });\n\n const body = (\n <span className=\"mt-1 block space-y-2\">\n <span className=\"block\">\n <span\n data-part=\"price\"\n className={cn(\n \"tabular-nums\",\n price.kind === \"not-offered\" ? \"text-xs text-muted\" : \"text-lg font-semibold leading-tight text-primary\",\n )}\n >\n {figure}\n </span>\n {priced && (\n <>\n {\" \"}\n <span className=\"text-xs text-secondary\">\n {interval === \"year\" ? labels.perYear : labels.perMonth}\n </span>{\" \"}\n <span className=\"block text-xs text-muted\">{labels.vatIncluded}</span>\n </>\n )}{\" \"}\n </span>\n {plan.description !== undefined && plan.description !== null && (\n <span className=\"block text-secondary\">{plan.description} </span>\n )}\n <SpanList name={labels.limits} items={limitLines} icon={<Gauge />} />\n <SpanList name={labels.features} items={[...(plan.features ?? [])]} icon={<Check />} />\n </span>\n );\n\n return (\n <ChoiceCard\n ref={ref}\n {...rest}\n type=\"radio\"\n value={plan.code}\n disabled={disabled || plan.disabled || price.kind === \"not-offered\"}\n data-plan={plan.code}\n data-relation={relation}\n title={\n <>\n {plan.name}\n {relation === \"current\" && (\n <>\n {\" \"}\n <Chip size=\"xs\" tone=\"brand\" shape=\"square\" caps className=\"ms-1 align-[0.0625rem]\">\n {labels.current}\n </Chip>\n </>\n )}\n </>\n }\n description={body}\n />\n );\n}\n\n/** What {@link PlanPickerProps.onChoose} is handed: everything `POST /billing/checkout`\n * takes (§4). */\nexport interface PlanChoice {\n plan: string;\n interval: BillingInterval;\n currency: BillingCurrency;\n}\n\nexport interface PlanPickerProps extends Omit<PlanDisplayProps, \"currency\" | \"interval\"> {\n /** The catalogue, in its order (`PlanSpec.sort`): cheapest first. The order decides\n * upgrade and downgrade. */\n plans: readonly BillingPlan[];\n /**\n * The plan the payer pays for now (`plan` of the overview while `active` or\n * `past_due`). Leave it out during a trial and after a plan ended: then every plan is\n * \"Choose …\", the trial's plan included, since none is paid for yet.\n */\n current?: string | null;\n /** The picked plan, controlled. `null` while none is. */\n value?: string | null;\n /** The picked plan, uncontrolled. Default: `current`, else nothing. */\n defaultValue?: string | null;\n onValueChange?: (plan: string) => void;\n /** The period, controlled. */\n interval?: BillingInterval;\n /** The period, uncontrolled. Default `year` — lead with yearly (§12.17). */\n defaultInterval?: BillingInterval;\n onIntervalChange?: (interval: BillingInterval) => void;\n /** The currency, controlled. */\n currency?: BillingCurrency;\n /** The currency, uncontrolled. Default: the account's stored currency is the app's to\n * pass here (§12.18); without it, {@link billingCurrencyFor} the kit's locale. */\n defaultCurrency?: BillingCurrency;\n onCurrencyChange?: (currency: BillingCurrency) => void;\n /** The currencies the payer may switch between. Default: every currency a plan has a\n * price in. The switch shows only for more than one. */\n currencies?: readonly BillingCurrency[];\n /** The button: the app starts its checkout (`POST /billing/checkout`, then off to the\n * provider's page). The kit sends no request. */\n onChoose: (choice: PlanChoice) => void;\n /** The checkout is being opened: the button's spinner, no second press. */\n pending?: boolean;\n /**\n * 0.42 (§16.2 decision 5, §16.8 B): the payer's free grant runs without an end —\n * `isPermanentGrant(overview)`. A purchase under it changes nothing the payer gets\n * while the grant runs (§12.11), so the button first opens a confirm —\n * `grantWarningTitle`, `grantWarningBody`, \"Buy anyway\" (`grantWarningAction`) and\n * \"Back\" — and {@link onChoose} runs only on \"Buy anyway\". Warned, not refused.\n *\n * Not while {@link switching}: then the pick opens `PlanSwitchConfirm`, which takes\n * the same `permanentGrant` and shows the warning above its figures — one warning,\n * in the dialog where the payer agrees, rather than a confirm before a confirm.\n */\n permanentGrant?: boolean;\n /**\n * 0.42: the overview's `switching` — the app sends this pick to `PlanSwitchConfirm`\n * (§15.2.10), not the checkout. Only decides where {@link permanentGrant}'s warning\n * shows; the button and {@link onChoose} are the same either way.\n */\n switching?: boolean;\n /** The provider's localised price for a plan, or `undefined` for the catalogue's —\n * see {@link PlanCardProps.pricePreview}. */\n pricePreview?: (plan: BillingPlan, choice: Omit<PlanChoice, \"plan\">) => ReactNode | undefined;\n /** A visible question over the cards. Without one the set is named \"Plans\". */\n legend?: ReactNode;\n /** Shared by the radios. Generated if omitted. */\n name?: string;\n /** Classes for the whole picker. */\n className?: string;\n /** Classes for the card grid. Default: one column, two from `sm`, three from `lg`. */\n gridClassName?: string;\n /** Classes for every card. */\n cardClassName?: string;\n}\n\n/** Every currency some plan has a price in, CHF first. */\nfunction pricedCurrencies(plans: readonly BillingPlan[]): BillingCurrency[] {\n const found = new Set<BillingCurrency>();\n for (const p of plans) for (const c of Object.keys(p.prices ?? {})) found.add(c as BillingCurrency);\n return ([\"CHF\", \"EUR\"] as const).filter((c) => found.has(c));\n}\n\n/** Every period some plan has a price for, yearly first. */\nfunction pricedIntervals(plans: readonly BillingPlan[]): BillingInterval[] {\n const found = new Set<BillingInterval>();\n for (const p of plans)\n for (const byInterval of Object.values(p.prices ?? {}))\n for (const [k, v] of Object.entries(byInterval ?? {})) if (typeof v === \"number\") found.add(k as BillingInterval);\n return BILLING_INTERVALS.filter((i) => found.has(i));\n}\n\n/**\n * The plans to choose from (§7): the period switch — yearly first (§12.17) — and, where\n * the payer may pay in either, the currency switch (§2.6); a {@link PlanCard} per plan in\n * a radio set; and one button that says what pressing it does: \"Upgrade to Pro\", \"Switch\n * to Free\", or \"Choose Pro\" with no plan paid for yet.\n *\n * One button under the set rather than one per card: each card IS a radio — a `<label>`\n * round an `<input>` — and a button inside a label is interactive content inside\n * interactive content. Picking and committing are two steps anyway: the price under the\n * picked card is what checkout will charge.\n *\n * The button never takes the write lock: billing itself always works, a read-only payer\n * must be able to pay (§3.3). It is off, with the reason on hover and focus, while\n * nothing is picked or the current plan is.\n *\n * 0.42 (§16.8 B): under a grant without an end (`permanentGrant`) the payer is warned\n * before paying for a plan that won't apply — here, in a confirm before `onChoose`, on\n * the way to the checkout; on the way to a switch (`switching`) in `PlanSwitchConfirm`\n * instead, so the warning shows once on either path.\n */\nexport function PlanPicker({\n plans,\n current,\n value: valueProp,\n defaultValue,\n onValueChange,\n interval: intervalProp,\n defaultInterval = \"year\",\n onIntervalChange,\n currency: currencyProp,\n defaultCurrency,\n onCurrencyChange,\n currencies: currenciesProp,\n onChoose,\n pending,\n permanentGrant = false,\n switching = false,\n pricePreview,\n legend,\n name,\n className,\n gridClassName,\n cardClassName,\n dimensionLabels,\n formatLimit,\n locale: localeProp,\n labels: labelsProp,\n}: PlanPickerProps) {\n const labels = useBillingLabels(labelsProp);\n const locale = useKitLocale(localeProp);\n const generated = useId();\n /** The choice the grant warning holds back until \"Buy anyway\". */\n const [warned, setWarned] = useState<PlanChoice | null>(null);\n const groupName = name ?? generated;\n const legendId = `${generated}-legend`;\n\n const [ownValue, setOwnValue] = useState<string | null>(() => defaultValue ?? current ?? null);\n const [ownInterval, setOwnInterval] = useState<BillingInterval>(defaultInterval);\n const [ownCurrency, setOwnCurrency] = useState<BillingCurrency>(() => defaultCurrency ?? billingCurrencyFor(locale));\n const value = valueProp !== undefined ? valueProp : ownValue;\n const interval = intervalProp ?? ownInterval;\n const currency = currencyProp ?? ownCurrency;\n\n const pick = (code: string) => {\n if (valueProp === undefined) setOwnValue(code);\n onValueChange?.(code);\n };\n const setInterval = (next: BillingInterval) => {\n if (intervalProp === undefined) setOwnInterval(next);\n onIntervalChange?.(next);\n };\n const setCurrency = (next: BillingCurrency) => {\n if (currencyProp === undefined) setOwnCurrency(next);\n onCurrencyChange?.(next);\n };\n\n const currencies = currenciesProp ?? pricedCurrencies(plans);\n const intervals = pricedIntervals(plans);\n const picked = value ? plans.find((p) => p.code.toLowerCase() === value.toLowerCase()) : undefined;\n const relation = picked ? planRelation(plans, picked.code, current) : undefined;\n const pickedPrice = picked ? planPrice(picked.prices, currency, interval) : undefined;\n\n const buttonText = !picked\n ? labels.choosePlan\n : relation === \"upgrade\"\n ? labels.upgrade(picked.name)\n : relation === \"downgrade\"\n ? labels.downgrade(picked.name)\n : labels.choose(picked.name);\n const blocked = !picked\n ? labels.pickFirst\n : relation === \"current\"\n ? labels.isCurrent\n : pickedPrice?.kind === \"not-offered\"\n ? labels.notOffered\n : undefined;\n\n return (\n <div\n className={cn(\"min-w-0 space-y-3\", className)}\n data-billing-interval={interval}\n data-billing-currency={currency}\n >\n {(intervals.length > 1 || currencies.length > 1) && (\n <div className=\"flex flex-wrap items-center gap-2\">\n {intervals.length > 1 && (\n <ToggleGroup\n aria-label={labels.interval}\n // Content-wide, so the two switches share a row and wrap only when narrow.\n className=\"w-auto\"\n options={intervals.map((i) => ({ value: i, label: i === \"year\" ? labels.yearly : labels.monthly }))}\n value={interval}\n onChange={setInterval}\n />\n )}\n {currencies.length > 1 && (\n <ToggleGroup\n aria-label={labels.currency}\n className=\"w-auto\"\n options={currencies.map((c) => ({ value: c, label: c }))}\n value={currency}\n onChange={setCurrency}\n />\n )}\n </div>\n )}\n <fieldset\n className=\"min-w-0\"\n aria-labelledby={legend !== undefined ? legendId : undefined}\n aria-label={legend === undefined ? labels.plans : undefined}\n >\n {legend !== undefined && (\n <legend id={legendId} className=\"mb-2 text-sm font-medium text-primary\">\n {legend}\n </legend>\n )}\n <div className={cn(\"grid grid-cols-1 gap-2 sm:grid-cols-2 lg:grid-cols-3\", gridClassName)}>\n {plans.map((plan) => (\n <PlanCard\n key={plan.code}\n plan={plan}\n name={groupName}\n currency={currency}\n interval={interval}\n relation={planRelation(plans, plan.code, current)}\n pricePreview={pricePreview?.(plan, { interval, currency })}\n checked={picked?.code === plan.code}\n onCheckedChange={(on) => on && pick(plan.code)}\n dimensionLabels={dimensionLabels}\n formatLimit={formatLimit}\n locale={locale}\n labels={labelsProp}\n className={cardClassName}\n />\n ))}\n </div>\n </fieldset>\n <div className=\"flex justify-end\">\n <Button\n type=\"button\"\n variant=\"default\"\n className=\"max-sm:w-full\"\n disabledReason={blocked}\n pending={pending}\n onClick={() => {\n if (!picked) return;\n const choice = { plan: picked.code, interval, currency };\n if (permanentGrant && !switching) setWarned(choice);\n else onChoose(choice);\n }}\n >\n {buttonText}\n </Button>\n </div>\n {permanentGrant && !switching && (\n <GrantWarningConfirm\n open={warned !== null}\n onClose={() => setWarned(null)}\n onConfirm={() => {\n if (warned) onChoose(warned);\n }}\n labels={labelsProp}\n />\n )}\n </div>\n );\n}\n\n/** {@link GrantWarningConfirmProps} before one of its two close props is required. */\ninterface GrantWarningConfirmFields {\n /** Shown, controlled. */\n open: boolean;\n /** \"Back\", Escape, the backdrop — and after \"Buy anyway\". 0.52: every other kit\n * dialog's `onClose` (CONTRIBUTING §4; refactor audit 2026-10-30, API-5). */\n onClose?: () => void;\n /** @deprecated 0.52 — `onClose`, as every other kit dialog; removed in 0.53. Called with\n * `false` when there is no `onClose`. */\n onOpenChange?: (open: boolean) => void;\n /** \"Buy anyway\": the app opens its checkout. The dialog closes itself after it. */\n onConfirm: () => void;\n /** Prop > `<UiKitProvider labels={{ billing }}>` > English: `grantWarningTitle`,\n * `grantWarningBody`, `grantWarningAction`. \"Back\" is `common.back`. */\n labels?: LabelOverride<BillingLabels>;\n}\n\n/** What {@link GrantWarningConfirm} takes: `onClose`, or alone the deprecated\n * `onOpenChange`, is required (0.52 code review: with neither it compiled, and nothing\n * could dismiss it). */\nexport type GrantWarningConfirmProps = WithDialogClose<GrantWarningConfirmFields>;\n\n/**\n * 0.42 (§16.8 B): the permanent-grant warning before a checkout — `grantWarningTitle`,\n * `grantWarningBody`, \"Back\" and \"Buy anyway\" (`grantWarningAction`). {@link PlanPicker}\n * shows it itself under `permanentGrant` when not `switching`. Exported for the path the\n * picker cannot see: an app that meant to switch, learned there is no subscription to\n * switch (keksdose: `/billing/plan/preview` → 409 `billing_not_subscribed`) and falls back\n * to a checkout — show this before that checkout, so a permanent-grant holder is still\n * warned. Carries `data-grant-warning`.\n */\nexport function GrantWarningConfirm({\n open,\n onClose,\n onOpenChange,\n onConfirm,\n labels: labelsProp,\n}: GrantWarningConfirmProps) {\n const labels = useBillingLabels(labelsProp);\n const common = useKitLabels(\"common\", DEFAULT_COMMON_LABELS);\n const close = dialogClose({ onClose, onOpenChange });\n return (\n <DialogFrame\n open={open}\n title={labels.grantWarningTitle}\n description={labels.grantWarningBody}\n onClose={close}\n data-grant-warning=\"\"\n actions={(lower) => (\n <>\n <Button variant=\"secondary\" onClick={lower}>\n {common.back}\n </Button>\n <Button\n variant=\"default\"\n onClick={() => {\n // Already closed, and lowering: a second press is no second checkout, for an\n // app that shows this dialog itself as much as for PlanPicker.\n if (!open) return;\n onConfirm();\n // The caller's close, at once — not `lower`, the panel's animated one, which\n // reports the close 220 ms later. PlanPicker clears the warned choice in it,\n // and the lowering panel is not inert: a double click on \"Buy anyway\" opened\n // the checkout twice (0.52 code review, plan-card.tsx). The caller's `open`\n // turning false still plays the exit.\n close();\n }}\n >\n {labels.grantWarningAction}\n </Button>\n </>\n )}\n />\n );\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/billing/plan-card.tsx"],"sourcesContent":["import { useId, useState } from \"react\";\nimport type { ReactNode } from \"react\";\nimport { Check, Gauge } from \"lucide-react\";\n\nimport { DEFAULT_COMMON_LABELS, useKitLabels, useKitLocale } from \"../i18n/kit-labels\";\nimport type { LabelOverride } from \"../i18n/kit-labels\";\nimport { formatNumber } from \"../lib/format\";\nimport { cn } from \"../lib/cn\";\nimport { ChoiceCard } from \"../components/choice-card\";\nimport type { ChoiceCardProps } from \"../components/choice-card\";\nimport { Chip } from \"../components/chip\";\nimport { ToggleGroup } from \"../components/toggle-group\";\nimport { Button } from \"../components/button\";\nimport { DialogFrame } from \"../components/dialog-frame\";\nimport { useBillingLabels } from \"./billing-labels\";\nimport type { BillingLabels } from \"./billing-labels\";\nimport { BILLING_INTERVALS, billingCurrencyFor, formatPlanPrice, planPrice } from \"./plan-price\";\nimport type { BillingCurrency, BillingInterval, PlanPrices } from \"./plan-price\";\n\n/**\n * One plan of the app's catalogue (docs/billing-harmonization.md §3.1) as a page shows it:\n * server-kit's `PlanSpec` from `GET /billing/plans`, plus the words, which are the app's\n * (display names and feature lines come from its i18n, never from the provider).\n */\nexport interface BillingPlan {\n /** The plan code, lowercase on the wire (§12.16); compared without case here. */\n code: string;\n /** The app's display name, in the reader's language: \"Pro\". */\n name: string;\n /** A line under the price. */\n description?: ReactNode;\n /** Gross prices in minor units, per currency and period (§3.1, §12.17). None at all —\n * or 0 — is a free plan. */\n prices?: PlanPrices | null;\n /** Dimension → limit; `null` is unlimited (§3.1). Shown in the object's order. */\n limits?: Readonly<Record<string, number | null>>;\n /** The app's feature lines. */\n features?: readonly ReactNode[];\n /** Not choosable — a plan being retired, say. */\n disabled?: boolean;\n}\n\n/**\n * How a plan stands to the one the payer pays for now: `current` is it, `upgrade` comes\n * after it in the catalogue's order (`PlanSpec.sort`), `downgrade` before it.\n */\nexport type PlanRelation = \"current\" | \"upgrade\" | \"downgrade\";\n\n/** The plan's place against `current`, by the order of `plans`; `undefined` without a\n * current plan (a trial, an ended plan) or for a code that is not in the list. */\nexport function planRelation(\n plans: readonly Pick<BillingPlan, \"code\">[],\n code: string,\n current: string | null | undefined,\n): PlanRelation | undefined {\n if (!current) return undefined;\n const index = (c: string) => plans.findIndex((p) => p.code.toLowerCase() === c.toLowerCase());\n const at = index(code);\n const now = index(current);\n if (at < 0 || now < 0) return undefined;\n return at === now ? \"current\" : at > now ? \"upgrade\" : \"downgrade\";\n}\n\n/** What both parts need to draw a plan. */\ninterface PlanDisplayProps {\n /** The currency the prices are shown in (§2.6). */\n currency: BillingCurrency;\n /** The period the prices are shown for. */\n interval: BillingInterval;\n /** The app's name of each limit's dimension (\"Budgets\"). A dimension without one is\n * shown by its key. */\n dimensionLabels?: Readonly<Record<string, string>>;\n /** How a limit's figure is written — kastlan's storage is bytes (`formatFileSize`).\n * Default: a number in the kit's locale. */\n formatLimit?: (dimension: string, value: number) => string;\n /** Over the provider's locale. */\n locale?: string;\n /** Prop > `<UiKitProvider labels={{ billing }}>` > English. */\n labels?: LabelOverride<BillingLabels>;\n}\n\nexport interface PlanCardProps\n extends\n Omit<ChoiceCardProps, \"type\" | \"title\" | \"description\" | \"icon\" | \"indeterminate\" | \"value\" | \"labels\">,\n PlanDisplayProps {\n plan: BillingPlan;\n /** See {@link PlanRelation}. `current` draws the \"Current plan\" chip — a word in the\n * card's name, never a colour alone. */\n relation?: PlanRelation;\n /**\n * The provider's own localised price in place of the catalogue's figure (§12.17) — a\n * Merchant of Record's price preview for the buyer's country, tax included. The period\n * and \"VAT included\" stay: the law asks for a gross price, and the card says so.\n */\n pricePreview?: ReactNode;\n}\n\n/** A list from spans (`role=\"list\"`): the card is a `<label>`, which may hold phrasing\n * content only, so a `<ul>` there would be invalid HTML. */\nfunction SpanList({ name, items, icon }: { name: string; items: ReactNode[]; icon: ReactNode }) {\n if (items.length === 0) return null;\n return (\n <span role=\"list\" aria-label={name} className=\"block space-y-1\">\n {items.map((item, i) => (\n <span role=\"listitem\" key={i} className=\"flex items-start gap-1.5 text-secondary\">\n <span aria-hidden className=\"mt-[0.0625rem] flex shrink-0 text-muted [&_svg]:size-3.5\">\n {icon}\n </span>\n <span className=\"min-w-0\">\n {item}\n {/* A space between the items' texts in the flattened description. */}{\" \"}\n </span>\n </span>\n ))}\n </span>\n );\n}\n\n/**\n * One plan as a {@link ChoiceCard} radio (§7): the name (and \"Current plan\"), the GROSS\n * price in the chosen currency and period through `formatMoney` (\"CHF 49 per year, VAT\n * included\", §12.17), the limits (\"Budgets: 3\", \"Seats: Unlimited\") and the app's feature\n * lines. A plan with prices but none for this currency and period is shown, \"Not offered\n * for this billing period\", and cannot be picked.\n *\n * A radio of a set: give the cards one `name`, or use {@link PlanPicker}, which does that\n * and owns the choice. Everything else a `ChoiceCard` takes goes to it — `checked`,\n * `onCheckedChange`, `name`, `className`. Carries `data-plan` and `data-relation`.\n */\nexport function PlanCard({\n // The radio `<input>` (ChoiceCard's): a React 19 ref prop since 0.34 (MOD-4, refactor\n // audit 2026-10), where this was `forwardRef`.\n ref,\n plan,\n relation,\n pricePreview,\n currency,\n interval,\n dimensionLabels,\n formatLimit,\n locale: localeProp,\n labels: labelsProp,\n disabled,\n ...rest\n}: PlanCardProps) {\n const labels = useBillingLabels(labelsProp);\n const common = useKitLabels(\"common\", DEFAULT_COMMON_LABELS);\n const locale = useKitLocale(localeProp);\n const price = planPrice(plan.prices, currency, interval);\n\n const priced = price.kind === \"price\";\n const figure = priced\n ? pricePreview !== undefined && pricePreview !== null\n ? pricePreview\n : formatPlanPrice(price.minor, currency, { locale })\n : price.kind === \"free\"\n ? labels.free\n : labels.notOffered;\n\n const limitLines = Object.entries(plan.limits ?? {}).map(([dimension, value]) => {\n const name = dimensionLabels?.[dimension] ?? dimension;\n const shown =\n value === null ? labels.unlimited : formatLimit ? formatLimit(dimension, value) : formatNumber(value, { locale });\n return common.fieldValue(name, shown);\n });\n\n const body = (\n <span className=\"mt-1 block space-y-2\">\n <span className=\"block\">\n <span\n data-part=\"price\"\n className={cn(\n \"tabular-nums\",\n price.kind === \"not-offered\" ? \"text-xs text-muted\" : \"text-lg font-semibold leading-tight text-primary\",\n )}\n >\n {figure}\n </span>\n {priced && (\n <>\n {\" \"}\n <span className=\"text-xs text-secondary\">\n {interval === \"year\" ? labels.perYear : labels.perMonth}\n </span>{\" \"}\n <span className=\"block text-xs text-muted\">{labels.vatIncluded}</span>\n </>\n )}{\" \"}\n </span>\n {plan.description !== undefined && plan.description !== null && (\n <span className=\"block text-secondary\">{plan.description} </span>\n )}\n <SpanList name={labels.limits} items={limitLines} icon={<Gauge />} />\n <SpanList name={labels.features} items={[...(plan.features ?? [])]} icon={<Check />} />\n </span>\n );\n\n return (\n <ChoiceCard\n ref={ref}\n {...rest}\n type=\"radio\"\n value={plan.code}\n disabled={disabled || plan.disabled || price.kind === \"not-offered\"}\n data-plan={plan.code}\n data-relation={relation}\n title={\n <>\n {plan.name}\n {relation === \"current\" && (\n <>\n {\" \"}\n <Chip size=\"xs\" tone=\"brand\" shape=\"square\" caps className=\"ms-1 align-[0.0625rem]\">\n {labels.current}\n </Chip>\n </>\n )}\n </>\n }\n description={body}\n />\n );\n}\n\n/** What {@link PlanPickerProps.onChoose} is handed: everything `POST /billing/checkout`\n * takes (§4). */\nexport interface PlanChoice {\n plan: string;\n interval: BillingInterval;\n currency: BillingCurrency;\n}\n\nexport interface PlanPickerProps extends Omit<PlanDisplayProps, \"currency\" | \"interval\"> {\n /** The catalogue, in its order (`PlanSpec.sort`): cheapest first. The order decides\n * upgrade and downgrade. */\n plans: readonly BillingPlan[];\n /**\n * The plan the payer pays for now (`plan` of the overview while `active` or\n * `past_due`). Leave it out during a trial and after a plan ended: then every plan is\n * \"Choose …\", the trial's plan included, since none is paid for yet.\n */\n current?: string | null;\n /** The picked plan, controlled. `null` while none is. */\n value?: string | null;\n /** The picked plan, uncontrolled. Default: `current`, else nothing. */\n defaultValue?: string | null;\n onValueChange?: (plan: string) => void;\n /** The period, controlled. */\n interval?: BillingInterval;\n /** The period, uncontrolled. Default `year` — lead with yearly (§12.17). */\n defaultInterval?: BillingInterval;\n onIntervalChange?: (interval: BillingInterval) => void;\n /** The currency, controlled. */\n currency?: BillingCurrency;\n /** The currency, uncontrolled. Default: the account's stored currency is the app's to\n * pass here (§12.18); without it, {@link billingCurrencyFor} the kit's locale. */\n defaultCurrency?: BillingCurrency;\n onCurrencyChange?: (currency: BillingCurrency) => void;\n /** The currencies the payer may switch between. Default: every currency a plan has a\n * price in. The switch shows only for more than one. */\n currencies?: readonly BillingCurrency[];\n /** The button: the app starts its checkout (`POST /billing/checkout`, then off to the\n * provider's page). The kit sends no request. */\n onChoose: (choice: PlanChoice) => void;\n /** The checkout is being opened: the button's spinner, no second press. */\n pending?: boolean;\n /**\n * 0.42 (§16.2 decision 5, §16.8 B): the payer's free grant runs without an end —\n * `isPermanentGrant(overview)`. A purchase under it changes nothing the payer gets\n * while the grant runs (§12.11), so the button first opens a confirm —\n * `grantWarningTitle`, `grantWarningBody`, \"Buy anyway\" (`grantWarningAction`) and\n * \"Back\" — and {@link onChoose} runs only on \"Buy anyway\". Warned, not refused.\n *\n * Not while {@link switching}: then the pick opens `PlanSwitchConfirm`, which takes\n * the same `permanentGrant` and shows the warning above its figures — one warning,\n * in the dialog where the payer agrees, rather than a confirm before a confirm.\n */\n permanentGrant?: boolean;\n /**\n * 0.42: the overview's `switching` — the app sends this pick to `PlanSwitchConfirm`\n * (§15.2.10), not the checkout. Only decides where {@link permanentGrant}'s warning\n * shows; the button and {@link onChoose} are the same either way.\n */\n switching?: boolean;\n /** The provider's localised price for a plan, or `undefined` for the catalogue's —\n * see {@link PlanCardProps.pricePreview}. */\n pricePreview?: (plan: BillingPlan, choice: Omit<PlanChoice, \"plan\">) => ReactNode | undefined;\n /** A visible question over the cards. Without one the set is named \"Plans\". */\n legend?: ReactNode;\n /** Shared by the radios. Generated if omitted. */\n name?: string;\n /** Classes for the whole picker. */\n className?: string;\n /** Classes for the card grid. Default: one column, two from `sm`, three from `lg`. */\n gridClassName?: string;\n /** Classes for every card. */\n cardClassName?: string;\n}\n\n/** Every currency some plan has a price in, CHF first. */\nfunction pricedCurrencies(plans: readonly BillingPlan[]): BillingCurrency[] {\n const found = new Set<BillingCurrency>();\n for (const p of plans) for (const c of Object.keys(p.prices ?? {})) found.add(c as BillingCurrency);\n return ([\"CHF\", \"EUR\"] as const).filter((c) => found.has(c));\n}\n\n/** Every period some plan has a price for, yearly first. */\nfunction pricedIntervals(plans: readonly BillingPlan[]): BillingInterval[] {\n const found = new Set<BillingInterval>();\n for (const p of plans)\n for (const byInterval of Object.values(p.prices ?? {}))\n for (const [k, v] of Object.entries(byInterval ?? {})) if (typeof v === \"number\") found.add(k as BillingInterval);\n return BILLING_INTERVALS.filter((i) => found.has(i));\n}\n\n/**\n * The plans to choose from (§7): the period switch — yearly first (§12.17) — and, where\n * the payer may pay in either, the currency switch (§2.6); a {@link PlanCard} per plan in\n * a radio set; and one button that says what pressing it does: \"Upgrade to Pro\", \"Switch\n * to Free\", or \"Choose Pro\" with no plan paid for yet.\n *\n * One button under the set rather than one per card: each card IS a radio — a `<label>`\n * round an `<input>` — and a button inside a label is interactive content inside\n * interactive content. Picking and committing are two steps anyway: the price under the\n * picked card is what checkout will charge.\n *\n * The button never takes the write lock: billing itself always works, a read-only payer\n * must be able to pay (§3.3). It is off, with the reason on hover and focus, while\n * nothing is picked or the current plan is.\n *\n * 0.42 (§16.8 B): under a grant without an end (`permanentGrant`) the payer is warned\n * before paying for a plan that won't apply — here, in a confirm before `onChoose`, on\n * the way to the checkout; on the way to a switch (`switching`) in `PlanSwitchConfirm`\n * instead, so the warning shows once on either path.\n */\nexport function PlanPicker({\n plans,\n current,\n value: valueProp,\n defaultValue,\n onValueChange,\n interval: intervalProp,\n defaultInterval = \"year\",\n onIntervalChange,\n currency: currencyProp,\n defaultCurrency,\n onCurrencyChange,\n currencies: currenciesProp,\n onChoose,\n pending,\n permanentGrant = false,\n switching = false,\n pricePreview,\n legend,\n name,\n className,\n gridClassName,\n cardClassName,\n dimensionLabels,\n formatLimit,\n locale: localeProp,\n labels: labelsProp,\n}: PlanPickerProps) {\n const labels = useBillingLabels(labelsProp);\n const locale = useKitLocale(localeProp);\n const generated = useId();\n /** The choice the grant warning holds back until \"Buy anyway\". */\n const [warned, setWarned] = useState<PlanChoice | null>(null);\n const groupName = name ?? generated;\n const legendId = `${generated}-legend`;\n\n const [ownValue, setOwnValue] = useState<string | null>(() => defaultValue ?? current ?? null);\n const [ownInterval, setOwnInterval] = useState<BillingInterval>(defaultInterval);\n const [ownCurrency, setOwnCurrency] = useState<BillingCurrency>(() => defaultCurrency ?? billingCurrencyFor(locale));\n const value = valueProp !== undefined ? valueProp : ownValue;\n const interval = intervalProp ?? ownInterval;\n const currency = currencyProp ?? ownCurrency;\n\n const pick = (code: string) => {\n if (valueProp === undefined) setOwnValue(code);\n onValueChange?.(code);\n };\n const setInterval = (next: BillingInterval) => {\n if (intervalProp === undefined) setOwnInterval(next);\n onIntervalChange?.(next);\n };\n const setCurrency = (next: BillingCurrency) => {\n if (currencyProp === undefined) setOwnCurrency(next);\n onCurrencyChange?.(next);\n };\n\n const currencies = currenciesProp ?? pricedCurrencies(plans);\n const intervals = pricedIntervals(plans);\n const picked = value ? plans.find((p) => p.code.toLowerCase() === value.toLowerCase()) : undefined;\n const relation = picked ? planRelation(plans, picked.code, current) : undefined;\n const pickedPrice = picked ? planPrice(picked.prices, currency, interval) : undefined;\n\n const buttonText = !picked\n ? labels.choosePlan\n : relation === \"upgrade\"\n ? labels.upgrade(picked.name)\n : relation === \"downgrade\"\n ? labels.downgrade(picked.name)\n : labels.choose(picked.name);\n const blocked = !picked\n ? labels.pickFirst\n : relation === \"current\"\n ? labels.isCurrent\n : pickedPrice?.kind === \"not-offered\"\n ? labels.notOffered\n : undefined;\n\n return (\n <div\n className={cn(\"min-w-0 space-y-3\", className)}\n data-billing-interval={interval}\n data-billing-currency={currency}\n >\n {(intervals.length > 1 || currencies.length > 1) && (\n <div className=\"flex flex-wrap items-center gap-2\">\n {intervals.length > 1 && (\n <ToggleGroup\n aria-label={labels.interval}\n // Content-wide, so the two switches share a row and wrap only when narrow.\n className=\"w-auto\"\n options={intervals.map((i) => ({ value: i, label: i === \"year\" ? labels.yearly : labels.monthly }))}\n value={interval}\n onChange={setInterval}\n />\n )}\n {currencies.length > 1 && (\n <ToggleGroup\n aria-label={labels.currency}\n className=\"w-auto\"\n options={currencies.map((c) => ({ value: c, label: c }))}\n value={currency}\n onChange={setCurrency}\n />\n )}\n </div>\n )}\n <fieldset\n className=\"min-w-0\"\n aria-labelledby={legend !== undefined ? legendId : undefined}\n aria-label={legend === undefined ? labels.plans : undefined}\n >\n {legend !== undefined && (\n <legend id={legendId} className=\"mb-2 text-sm font-medium text-primary\">\n {legend}\n </legend>\n )}\n <div className={cn(\"grid grid-cols-1 gap-2 sm:grid-cols-2 lg:grid-cols-3\", gridClassName)}>\n {plans.map((plan) => (\n <PlanCard\n key={plan.code}\n plan={plan}\n name={groupName}\n currency={currency}\n interval={interval}\n relation={planRelation(plans, plan.code, current)}\n pricePreview={pricePreview?.(plan, { interval, currency })}\n checked={picked?.code === plan.code}\n onCheckedChange={(on) => on && pick(plan.code)}\n dimensionLabels={dimensionLabels}\n formatLimit={formatLimit}\n locale={locale}\n labels={labelsProp}\n className={cardClassName}\n />\n ))}\n </div>\n </fieldset>\n <div className=\"flex justify-end\">\n <Button\n type=\"button\"\n variant=\"default\"\n className=\"max-sm:w-full\"\n disabledReason={blocked}\n pending={pending}\n onClick={() => {\n if (!picked) return;\n const choice = { plan: picked.code, interval, currency };\n if (permanentGrant && !switching) setWarned(choice);\n else onChoose(choice);\n }}\n >\n {buttonText}\n </Button>\n </div>\n {permanentGrant && !switching && (\n <GrantWarningConfirm\n open={warned !== null}\n onClose={() => setWarned(null)}\n onConfirm={() => {\n if (warned) onChoose(warned);\n }}\n labels={labelsProp}\n />\n )}\n </div>\n );\n}\n\nexport interface GrantWarningConfirmProps {\n /** Shown, controlled. */\n open: boolean;\n /** \"Back\", Escape, the backdrop — and after \"Buy anyway\". 0.52: every other kit\n * dialog's `onClose` (CONTRIBUTING §4; refactor audit 2026-10-30, API-5); required\n * since 0.53 removed `onOpenChange` (deprecated in 0.52). */\n onClose: () => void;\n /** \"Buy anyway\": the app opens its checkout. The dialog closes itself after it. */\n onConfirm: () => void;\n /** Prop > `<UiKitProvider labels={{ billing }}>` > English: `grantWarningTitle`,\n * `grantWarningBody`, `grantWarningAction`. \"Back\" is `common.back`. */\n labels?: LabelOverride<BillingLabels>;\n}\n\n/**\n * 0.42 (§16.8 B): the permanent-grant warning before a checkout — `grantWarningTitle`,\n * `grantWarningBody`, \"Back\" and \"Buy anyway\" (`grantWarningAction`). {@link PlanPicker}\n * shows it itself under `permanentGrant` when not `switching`. Exported for the path the\n * picker cannot see: an app that meant to switch, learned there is no subscription to\n * switch (keksdose: `/billing/plan/preview` → 409 `billing_not_subscribed`) and falls back\n * to a checkout — show this before that checkout, so a permanent-grant holder is still\n * warned. Carries `data-grant-warning`.\n */\nexport function GrantWarningConfirm({ open, onClose, onConfirm, labels: labelsProp }: GrantWarningConfirmProps) {\n const labels = useBillingLabels(labelsProp);\n const common = useKitLabels(\"common\", DEFAULT_COMMON_LABELS);\n return (\n <DialogFrame\n open={open}\n title={labels.grantWarningTitle}\n description={labels.grantWarningBody}\n onClose={onClose}\n data-grant-warning=\"\"\n actions={(lower) => (\n <>\n <Button variant=\"secondary\" onClick={lower}>\n {common.back}\n </Button>\n <Button\n variant=\"default\"\n onClick={() => {\n // Already closed, and lowering: a second press is no second checkout, for an\n // app that shows this dialog itself as much as for PlanPicker.\n if (!open) return;\n onConfirm();\n // The caller's close, at once — not `lower`, the panel's animated one, which\n // reports the close 220 ms later. PlanPicker clears the warned choice in it,\n // and the lowering panel is not inert: a double click on \"Buy anyway\" opened\n // the checkout twice (0.52 code review, plan-card.tsx). The caller's `open`\n // turning false still plays the exit.\n onClose();\n }}\n >\n {labels.grantWarningAction}\n </Button>\n </>\n )}\n />\n );\n}\n"],"mappings":"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","names":[]}
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import type { LabelOverride } from "../i18n/kit-labels.js";
|
|
2
2
|
import type { PluralForms } from "../i18n/plurals.js";
|
|
3
3
|
import type { BillingLabels } from "./billing-labels.js";
|
|
4
|
-
import type { WithDialogClose } from "./dialog-close.js";
|
|
5
4
|
import type { PlanSwitchPreview } from "./billing-overview.js";
|
|
6
5
|
import type { BillingInterval } from "./plan-price.js";
|
|
7
6
|
/**
|
|
@@ -22,16 +21,13 @@ export interface PlanSwitchSide {
|
|
|
22
21
|
plan: string;
|
|
23
22
|
interval: BillingInterval;
|
|
24
23
|
}
|
|
25
|
-
|
|
26
|
-
interface PlanSwitchConfirmFields {
|
|
24
|
+
export interface PlanSwitchConfirmProps {
|
|
27
25
|
open: boolean;
|
|
28
26
|
/** The payer dismissed it — Cancel, Escape, the backdrop. Confirm leaves it open: close
|
|
29
27
|
* it once the switch answers. 0.52: every other kit dialog's `onClose` (CONTRIBUTING
|
|
30
|
-
* §4; refactor audit 2026-10-30, API-5).
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
* `false` when there is no `onClose`. */
|
|
34
|
-
onOpenChange?: (open: boolean) => void;
|
|
28
|
+
* §4; refactor audit 2026-10-30, API-5); required since 0.53 removed `onOpenChange`
|
|
29
|
+
* (deprecated in 0.52). */
|
|
30
|
+
onClose: () => void;
|
|
35
31
|
/** What the payer pays now: the overview's `paid`. */
|
|
36
32
|
from: PlanSwitchSide;
|
|
37
33
|
/** The pick. */
|
|
@@ -70,10 +66,6 @@ interface PlanSwitchConfirmFields {
|
|
|
70
66
|
/** Prop > `<UiKitProvider labels={{ billing }}>` > English. */
|
|
71
67
|
labels?: LabelOverride<BillingLabels>;
|
|
72
68
|
}
|
|
73
|
-
/** What {@link PlanSwitchConfirm} takes: `onClose`, or alone the deprecated
|
|
74
|
-
* `onOpenChange`, is required (0.52 code review: with neither it compiled, and nothing
|
|
75
|
-
* could dismiss it). */
|
|
76
|
-
export type PlanSwitchConfirmProps = WithDialogClose<PlanSwitchConfirmFields>;
|
|
77
69
|
/**
|
|
78
70
|
* The confirm for a plan switch (§15.2.10): from → to, each with its billing period;
|
|
79
71
|
* Paddle's preview — "Charged now" (an upgrade) or "Credited towards your next charges"
|
|
@@ -96,5 +88,4 @@ export type PlanSwitchConfirmProps = WithDialogClose<PlanSwitchConfirmFields>;
|
|
|
96
88
|
* change plan, as they may pay. A demo lock still holds it, as the server's demo guard
|
|
97
89
|
* does. The kit sends no request.
|
|
98
90
|
*/
|
|
99
|
-
export declare function PlanSwitchConfirm({ open, onClose,
|
|
100
|
-
export {};
|
|
91
|
+
export declare function PlanSwitchConfirm({ open, onClose, from, to, preview, pending, planName, dimensionLabels, formatLimit, onConfirm, locale: localeProp, permanentGrant, labels: labelsProp, }: PlanSwitchConfirmProps): import("react").JSX.Element;
|
|
@@ -12,12 +12,10 @@ import { Spinner } from "../components/spinner.js";
|
|
|
12
12
|
import { COMMIT_EXCEPT_BILLING } from "../components/write-lock.js";
|
|
13
13
|
import { DEFAULT_PLAN_CHANGE_LABELS } from "./plan-change-labels.js";
|
|
14
14
|
import { useBillingLabels } from "./billing-labels.js";
|
|
15
|
-
import { dialogClose } from "./dialog-close.js";
|
|
16
15
|
import { minorToMajor } from "./plan-price.js";
|
|
17
16
|
function PlanSwitchConfirm({
|
|
18
17
|
open,
|
|
19
18
|
onClose,
|
|
20
|
-
onOpenChange,
|
|
21
19
|
from,
|
|
22
20
|
to,
|
|
23
21
|
preview,
|
|
@@ -56,7 +54,7 @@ function PlanSwitchConfirm({
|
|
|
56
54
|
open,
|
|
57
55
|
title: labels.switchTitle,
|
|
58
56
|
description: labels.switchFromTo(side(from), side(to)),
|
|
59
|
-
onClose
|
|
57
|
+
onClose,
|
|
60
58
|
busy: pending,
|
|
61
59
|
"aria-busy": preview === void 0 || void 0,
|
|
62
60
|
actions: (close) => /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/billing/plan-switch.tsx"],"sourcesContent":["import { useKitLabels, useKitLocale } from \"../i18n/kit-labels\";\nimport type { LabelOverride } from \"../i18n/kit-labels\";\nimport { DEFAULT_COMMON_LABELS } from \"../i18n/shared-labels\";\nimport { pluralForm } from \"../i18n/plurals\";\nimport type { PluralForms } from \"../i18n/plurals\";\nimport { useDayFormatter } from \"../lib/day-format\";\nimport { formatMoney, formatNumber } from \"../lib/format\";\nimport { AlertBanner } from \"../components/alert-banner\";\nimport { Button } from \"../components/button\";\nimport { DialogFrame } from \"../components/dialog-frame\";\nimport { Spinner } from \"../components/spinner\";\nimport { COMMIT_EXCEPT_BILLING } from \"../components/write-lock\";\nimport { DEFAULT_PLAN_CHANGE_LABELS } from \"./plan-change-labels\";\nimport { useBillingLabels } from \"./billing-labels\";\nimport type { BillingLabels } from \"./billing-labels\";\nimport { dialogClose } from \"./dialog-close\";\nimport type { WithDialogClose } from \"./dialog-close\";\nimport type { PlanSwitchPreview } from \"./billing-overview\";\nimport { minorToMajor } from \"./plan-price\";\nimport type { BillingInterval } from \"./plan-price\";\n\n/**\n * The payer's plan switch (docs/billing-harmonization.md §15.2.10, ui-kit 0.39): built\n * once in the kits and used by all three apps, instead of Paddle's portal. While a\n * provider subscription runs, the app sends a `PlanPicker` pick to\n * `POST /billing/plan/preview` and opens this dialog with the answer; Confirm is the\n * app's `POST /billing/plan`. Otherwise the pick goes to the checkout, as before.\n *\n * The switch is immediate and prorated: an upgrade charges the difference now, a\n * downgrade credits the unused part to the customer's balance, which Paddle takes off\n * the next charges. So the payer agrees to a figure — Paddle's preview — and sees the\n * dimensions over the new plan's limit before agreeing (§3.4: a limit gates creation\n * only, so the switch is allowed).\n */\n\n/** One side of a switch: a plan code and its billing period. */\nexport interface PlanSwitchSide {\n plan: string;\n interval: BillingInterval;\n}\n\n/** {@link PlanSwitchConfirmProps} before one of its two close props is required. */\ninterface PlanSwitchConfirmFields {\n open: boolean;\n /** The payer dismissed it — Cancel, Escape, the backdrop. Confirm leaves it open: close\n * it once the switch answers. 0.52: every other kit dialog's `onClose` (CONTRIBUTING\n * §4; refactor audit 2026-10-30, API-5). */\n onClose?: () => void;\n /** @deprecated 0.52 — `onClose`, as every other kit dialog; removed in 0.53. Called with\n * `false` when there is no `onClose`. */\n onOpenChange?: (open: boolean) => void;\n /** What the payer pays now: the overview's `paid`. */\n from: PlanSwitchSide;\n /** The pick. */\n to: PlanSwitchSide;\n /** `POST /billing/plan/preview`'s answer. Undefined while it loads: a spinner, and\n * Confirm is held. */\n preview?: PlanSwitchPreview;\n /** `POST /billing/plan` is under way: Confirm's spinner, no second press, and neither\n * Escape, the backdrop nor Back closes it. */\n pending?: boolean;\n /** The plan's display name, the app's (§3.1). Default: the code. */\n planName?: (code: string) => string;\n /**\n * The app's word per dimension, for the over-limit lines. A heading's word (\"Budgets\")\n * reads \"Budgets: 7 of 5\" (`billing.limitUsageLine`); its plural forms\n * (`{ one: \"budget\", other: \"budgets\" }`) read \"7 of 5 budgets\" (`planChange.usageOf`,\n * the form the limit takes). A dimension without one is named by its key.\n */\n dimensionLabels?: Readonly<Record<string, string | PluralForms>>;\n /** How a usage or limit figure is written. Default: a number in the locale. */\n formatLimit?: (dimension: string, value: number) => string;\n /** Confirm: the app's `POST /billing/plan` with `{plan, interval}` of `to`. */\n onConfirm: () => void;\n /** The figures' and the date's locale. Default: the provider's. The next charge's date\n * is written by the `<UiKitProvider formatDate>` when the app set one (0.52). */\n locale?: string;\n /**\n * 0.42 (§16.2 decision 5, §16.8 B): the payer's free grant runs without an end —\n * `isPermanentGrant(overview)`. The switch changes the PAID plan, not the applied one\n * (§12.11), so the dialog says so above its figures (`grantWarningTitle`,\n * `grantWarningBody`) before the payer agrees. Confirm stays Confirm: the warning is\n * this dialog's own, and `PlanPicker` — told `switching` — shows none of its own on\n * the way here, so the payer reads it once.\n */\n permanentGrant?: boolean;\n /** Prop > `<UiKitProvider labels={{ billing }}>` > English. */\n labels?: LabelOverride<BillingLabels>;\n}\n\n/** What {@link PlanSwitchConfirm} takes: `onClose`, or alone the deprecated\n * `onOpenChange`, is required (0.52 code review: with neither it compiled, and nothing\n * could dismiss it). */\nexport type PlanSwitchConfirmProps = WithDialogClose<PlanSwitchConfirmFields>;\n\n/**\n * The confirm for a plan switch (§15.2.10): from → to, each with its billing period;\n * Paddle's preview — \"Charged now\" (an upgrade) or \"Credited towards your next charges\"\n * (a downgrade), and the next charge with its date — in the subscription's currency;\n * the dimensions over the new plan's limit; and Confirm.\n *\n * const preview = useMutation({ mutationFn: api.previewPlanSwitch });\n * <PlanSwitchConfirm\n * open={pick !== null}\n * onClose={() => setPick(null)}\n * from={overview.paid}\n * to={pick}\n * preview={preview.data}\n * pending={switching.isPending}\n * planName={planName}\n * onConfirm={() => switching.mutate(pick)}\n * />\n *\n * It takes no billing lock (`COMMIT_EXCEPT_BILLING` built in): a read-only payer may\n * change plan, as they may pay. A demo lock still holds it, as the server's demo guard\n * does. The kit sends no request.\n */\nexport function PlanSwitchConfirm({\n open,\n onClose,\n onOpenChange,\n from,\n to,\n preview,\n pending = false,\n planName = (code) => code,\n dimensionLabels,\n formatLimit,\n onConfirm,\n locale: localeProp,\n permanentGrant = false,\n labels: labelsProp,\n}: PlanSwitchConfirmProps) {\n const labels = useBillingLabels(labelsProp);\n const planChange = useKitLabels(\"planChange\", DEFAULT_PLAN_CHANGE_LABELS);\n const common = useKitLabels(\"common\", DEFAULT_COMMON_LABELS);\n const locale = useKitLocale(localeProp);\n const day = useDayFormatter(undefined, localeProp);\n\n const period = (interval: BillingInterval) => (interval === \"year\" ? labels.yearly : labels.monthly);\n const side = (s: PlanSwitchSide) => `${planName(s.plan.toLowerCase())} (${period(s.interval)})`;\n const money = (minor: number, currency: string) => formatMoney(minorToMajor(minor, currency), currency, { locale });\n const figure = (dimension: string, value: number) =>\n formatLimit ? formatLimit(dimension, value) : formatNumber(value, { locale });\n\n /** One line per dimension over the new limit, in the word's own sentence. */\n const overLimit = (p: PlanSwitchPreview): string[] =>\n (p.over_limit ?? []).flatMap((dimension) => {\n const limit = p.limits?.[dimension];\n const used = p.usage?.[dimension];\n if (limit === null || limit === undefined || used === undefined) return [];\n const word = dimensionLabels?.[dimension] ?? dimension;\n return [\n typeof word === \"string\"\n ? labels.limitUsageLine(word, figure(dimension, used), figure(dimension, limit))\n : planChange.usageOf(figure(dimension, used), figure(dimension, limit), pluralForm(limit, word, locale)),\n ];\n });\n\n const credit = preview !== undefined && preview.now_minor < 0;\n const lines = preview ? overLimit(preview) : [];\n\n return (\n <DialogFrame\n open={open}\n title={labels.switchTitle}\n description={labels.switchFromTo(side(from), side(to))}\n onClose={dialogClose({ onClose, onOpenChange })}\n busy={pending}\n // A preview that loads is busy too, but holds nothing: Cancel stays a way out.\n aria-busy={preview === undefined || undefined}\n actions={(close) => (\n <>\n <Button variant=\"secondary\" onClick={close} disabled={pending}>\n {common.cancel}\n </Button>\n <Button\n commit={COMMIT_EXCEPT_BILLING}\n pending={pending}\n disabled={preview === undefined}\n onClick={() => {\n if (preview !== undefined && !pending) onConfirm();\n }}\n >\n {labels.switchConfirm}\n </Button>\n </>\n )}\n >\n {permanentGrant && (\n <AlertBanner tone=\"warning\" size=\"sm\" data-grant-warning=\"\">\n <strong className=\"block font-medium\">{labels.grantWarningTitle}</strong> {labels.grantWarningBody}\n </AlertBanner>\n )}\n {preview === undefined ? (\n <div className=\"flex justify-center py-4\">\n <Spinner showLabel />\n </div>\n ) : (\n <>\n <dl className=\"grid grid-cols-[minmax(0,1fr)_auto] gap-x-4 gap-y-1 text-sm\" data-plan-switch-preview>\n <dt className=\"text-secondary\">{credit ? labels.switchCredited : labels.switchChargedNow}</dt>\n <dd className=\"text-right font-medium tabular-nums\" data-now={credit ? \"credit\" : \"charge\"}>\n {money(Math.abs(preview.now_minor), preview.currency)}\n </dd>\n {preview.next_billed_at && preview.next_minor != null && (\n <>\n <dt className=\"text-secondary\">{labels.switchNextCharge(day(preview.next_billed_at))}</dt>\n <dd className=\"text-right tabular-nums\">{money(preview.next_minor, preview.currency)}</dd>\n </>\n )}\n </dl>\n {lines.length > 0 && (\n <div className=\"space-y-1 text-sm\" data-plan-switch-over-limit>\n <p>{planChange.overLimit}</p>\n <ul className=\"list-disc space-y-0.5 ps-5 text-secondary\">\n {lines.map((line) => (\n <li key={line}>{line}</li>\n ))}\n </ul>\n </div>\n )}\n </>\n )}\n </DialogFrame>\n );\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/billing/plan-switch.tsx"],"sourcesContent":["import { useKitLabels, useKitLocale } from \"../i18n/kit-labels\";\nimport type { LabelOverride } from \"../i18n/kit-labels\";\nimport { DEFAULT_COMMON_LABELS } from \"../i18n/shared-labels\";\nimport { pluralForm } from \"../i18n/plurals\";\nimport type { PluralForms } from \"../i18n/plurals\";\nimport { useDayFormatter } from \"../lib/day-format\";\nimport { formatMoney, formatNumber } from \"../lib/format\";\nimport { AlertBanner } from \"../components/alert-banner\";\nimport { Button } from \"../components/button\";\nimport { DialogFrame } from \"../components/dialog-frame\";\nimport { Spinner } from \"../components/spinner\";\nimport { COMMIT_EXCEPT_BILLING } from \"../components/write-lock\";\nimport { DEFAULT_PLAN_CHANGE_LABELS } from \"./plan-change-labels\";\nimport { useBillingLabels } from \"./billing-labels\";\nimport type { BillingLabels } from \"./billing-labels\";\nimport type { PlanSwitchPreview } from \"./billing-overview\";\nimport { minorToMajor } from \"./plan-price\";\nimport type { BillingInterval } from \"./plan-price\";\n\n/**\n * The payer's plan switch (docs/billing-harmonization.md §15.2.10, ui-kit 0.39): built\n * once in the kits and used by all three apps, instead of Paddle's portal. While a\n * provider subscription runs, the app sends a `PlanPicker` pick to\n * `POST /billing/plan/preview` and opens this dialog with the answer; Confirm is the\n * app's `POST /billing/plan`. Otherwise the pick goes to the checkout, as before.\n *\n * The switch is immediate and prorated: an upgrade charges the difference now, a\n * downgrade credits the unused part to the customer's balance, which Paddle takes off\n * the next charges. So the payer agrees to a figure — Paddle's preview — and sees the\n * dimensions over the new plan's limit before agreeing (§3.4: a limit gates creation\n * only, so the switch is allowed).\n */\n\n/** One side of a switch: a plan code and its billing period. */\nexport interface PlanSwitchSide {\n plan: string;\n interval: BillingInterval;\n}\n\nexport interface PlanSwitchConfirmProps {\n open: boolean;\n /** The payer dismissed it — Cancel, Escape, the backdrop. Confirm leaves it open: close\n * it once the switch answers. 0.52: every other kit dialog's `onClose` (CONTRIBUTING\n * §4; refactor audit 2026-10-30, API-5); required since 0.53 removed `onOpenChange`\n * (deprecated in 0.52). */\n onClose: () => void;\n /** What the payer pays now: the overview's `paid`. */\n from: PlanSwitchSide;\n /** The pick. */\n to: PlanSwitchSide;\n /** `POST /billing/plan/preview`'s answer. Undefined while it loads: a spinner, and\n * Confirm is held. */\n preview?: PlanSwitchPreview;\n /** `POST /billing/plan` is under way: Confirm's spinner, no second press, and neither\n * Escape, the backdrop nor Back closes it. */\n pending?: boolean;\n /** The plan's display name, the app's (§3.1). Default: the code. */\n planName?: (code: string) => string;\n /**\n * The app's word per dimension, for the over-limit lines. A heading's word (\"Budgets\")\n * reads \"Budgets: 7 of 5\" (`billing.limitUsageLine`); its plural forms\n * (`{ one: \"budget\", other: \"budgets\" }`) read \"7 of 5 budgets\" (`planChange.usageOf`,\n * the form the limit takes). A dimension without one is named by its key.\n */\n dimensionLabels?: Readonly<Record<string, string | PluralForms>>;\n /** How a usage or limit figure is written. Default: a number in the locale. */\n formatLimit?: (dimension: string, value: number) => string;\n /** Confirm: the app's `POST /billing/plan` with `{plan, interval}` of `to`. */\n onConfirm: () => void;\n /** The figures' and the date's locale. Default: the provider's. The next charge's date\n * is written by the `<UiKitProvider formatDate>` when the app set one (0.52). */\n locale?: string;\n /**\n * 0.42 (§16.2 decision 5, §16.8 B): the payer's free grant runs without an end —\n * `isPermanentGrant(overview)`. The switch changes the PAID plan, not the applied one\n * (§12.11), so the dialog says so above its figures (`grantWarningTitle`,\n * `grantWarningBody`) before the payer agrees. Confirm stays Confirm: the warning is\n * this dialog's own, and `PlanPicker` — told `switching` — shows none of its own on\n * the way here, so the payer reads it once.\n */\n permanentGrant?: boolean;\n /** Prop > `<UiKitProvider labels={{ billing }}>` > English. */\n labels?: LabelOverride<BillingLabels>;\n}\n\n/**\n * The confirm for a plan switch (§15.2.10): from → to, each with its billing period;\n * Paddle's preview — \"Charged now\" (an upgrade) or \"Credited towards your next charges\"\n * (a downgrade), and the next charge with its date — in the subscription's currency;\n * the dimensions over the new plan's limit; and Confirm.\n *\n * const preview = useMutation({ mutationFn: api.previewPlanSwitch });\n * <PlanSwitchConfirm\n * open={pick !== null}\n * onClose={() => setPick(null)}\n * from={overview.paid}\n * to={pick}\n * preview={preview.data}\n * pending={switching.isPending}\n * planName={planName}\n * onConfirm={() => switching.mutate(pick)}\n * />\n *\n * It takes no billing lock (`COMMIT_EXCEPT_BILLING` built in): a read-only payer may\n * change plan, as they may pay. A demo lock still holds it, as the server's demo guard\n * does. The kit sends no request.\n */\nexport function PlanSwitchConfirm({\n open,\n onClose,\n from,\n to,\n preview,\n pending = false,\n planName = (code) => code,\n dimensionLabels,\n formatLimit,\n onConfirm,\n locale: localeProp,\n permanentGrant = false,\n labels: labelsProp,\n}: PlanSwitchConfirmProps) {\n const labels = useBillingLabels(labelsProp);\n const planChange = useKitLabels(\"planChange\", DEFAULT_PLAN_CHANGE_LABELS);\n const common = useKitLabels(\"common\", DEFAULT_COMMON_LABELS);\n const locale = useKitLocale(localeProp);\n const day = useDayFormatter(undefined, localeProp);\n\n const period = (interval: BillingInterval) => (interval === \"year\" ? labels.yearly : labels.monthly);\n const side = (s: PlanSwitchSide) => `${planName(s.plan.toLowerCase())} (${period(s.interval)})`;\n const money = (minor: number, currency: string) => formatMoney(minorToMajor(minor, currency), currency, { locale });\n const figure = (dimension: string, value: number) =>\n formatLimit ? formatLimit(dimension, value) : formatNumber(value, { locale });\n\n /** One line per dimension over the new limit, in the word's own sentence. */\n const overLimit = (p: PlanSwitchPreview): string[] =>\n (p.over_limit ?? []).flatMap((dimension) => {\n const limit = p.limits?.[dimension];\n const used = p.usage?.[dimension];\n if (limit === null || limit === undefined || used === undefined) return [];\n const word = dimensionLabels?.[dimension] ?? dimension;\n return [\n typeof word === \"string\"\n ? labels.limitUsageLine(word, figure(dimension, used), figure(dimension, limit))\n : planChange.usageOf(figure(dimension, used), figure(dimension, limit), pluralForm(limit, word, locale)),\n ];\n });\n\n const credit = preview !== undefined && preview.now_minor < 0;\n const lines = preview ? overLimit(preview) : [];\n\n return (\n <DialogFrame\n open={open}\n title={labels.switchTitle}\n description={labels.switchFromTo(side(from), side(to))}\n onClose={onClose}\n busy={pending}\n // A preview that loads is busy too, but holds nothing: Cancel stays a way out.\n aria-busy={preview === undefined || undefined}\n actions={(close) => (\n <>\n <Button variant=\"secondary\" onClick={close} disabled={pending}>\n {common.cancel}\n </Button>\n <Button\n commit={COMMIT_EXCEPT_BILLING}\n pending={pending}\n disabled={preview === undefined}\n onClick={() => {\n if (preview !== undefined && !pending) onConfirm();\n }}\n >\n {labels.switchConfirm}\n </Button>\n </>\n )}\n >\n {permanentGrant && (\n <AlertBanner tone=\"warning\" size=\"sm\" data-grant-warning=\"\">\n <strong className=\"block font-medium\">{labels.grantWarningTitle}</strong> {labels.grantWarningBody}\n </AlertBanner>\n )}\n {preview === undefined ? (\n <div className=\"flex justify-center py-4\">\n <Spinner showLabel />\n </div>\n ) : (\n <>\n <dl className=\"grid grid-cols-[minmax(0,1fr)_auto] gap-x-4 gap-y-1 text-sm\" data-plan-switch-preview>\n <dt className=\"text-secondary\">{credit ? labels.switchCredited : labels.switchChargedNow}</dt>\n <dd className=\"text-right font-medium tabular-nums\" data-now={credit ? \"credit\" : \"charge\"}>\n {money(Math.abs(preview.now_minor), preview.currency)}\n </dd>\n {preview.next_billed_at && preview.next_minor != null && (\n <>\n <dt className=\"text-secondary\">{labels.switchNextCharge(day(preview.next_billed_at))}</dt>\n <dd className=\"text-right tabular-nums\">{money(preview.next_minor, preview.currency)}</dd>\n </>\n )}\n </dl>\n {lines.length > 0 && (\n <div className=\"space-y-1 text-sm\" data-plan-switch-over-limit>\n <p>{planChange.overLimit}</p>\n <ul className=\"list-disc space-y-0.5 ps-5 text-secondary\">\n {lines.map((line) => (\n <li key={line}>{line}</li>\n ))}\n </ul>\n </div>\n )}\n </>\n )}\n </DialogFrame>\n );\n}\n"],"mappings":"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","names":[]}
|
package/dist/chart.d.ts
CHANGED
|
@@ -1,23 +1,12 @@
|
|
|
1
1
|
export * from "./components/chart.js";
|
|
2
|
-
export
|
|
3
|
-
export {
|
|
4
|
-
/** @deprecated 0.52 — `chartEdge`, which measures a fill against it (API-4); removed in 0.53 */
|
|
5
|
-
EDGE_MIN_CONTRAST,
|
|
6
|
-
/** @deprecated 0.52 — `--chart-ink` (`CHART_ROLES.outline`), the same ink as a token (API-4); removed in 0.53 */
|
|
7
|
-
CHART_INK_HEX, } from "./theme/chart-palette.js";
|
|
2
|
+
export { PALETTE_SIZE, CVD_ORDER, paletteFor, CHART_COLORS, CHART_ROLES, CHART_MUTED_HEX, chartEdge, PALETTE_HEX, HEATMAP_HEX, lerpHex, textOn, type PaletteForOptions, type HeatStops, } from "./theme/chart-palette.js";
|
|
8
3
|
export * from "./components/treemap.js";
|
|
9
4
|
export * from "./components/treemap-labels.js";
|
|
10
|
-
export
|
|
11
|
-
export {
|
|
12
|
-
/** @deprecated 0.52 — `SeriesChart`, which places its tooltip with it (API-4); removed in 0.53 */
|
|
13
|
-
resolveTooltipPlacement, } from "./components/series-chart.js";
|
|
5
|
+
export { AXIS_TICK_WIDTH, AXIS_TITLE_STRIP, anchoredBand, axisBandWidth, mergeSeries, oneAxis, padBand, paddedDomain, seriesKey, soleSeriesColor, visibleSeries, seriesLegendEntries, StaticSeriesChart, SeriesChart, type TimeTickUnit, type SeriesChartAxisBudget, type SeriesSource, type SeriesChartSeries, type SeriesChartType, type SeriesChartPoint, type SeriesChartRow, type SeriesChartXValue, type SeriesChartTickValues, type SeriesChartAxis, type SeriesChartXTick, type SeriesChartX, type SeriesChartTooltipPlacement, type SeriesChartTooltip, type SeriesChartSpan, type SeriesChartTone, type SeriesChartReference, type SeriesChartMarker, type SeriesChartHit, type SeriesChartProps, type SeriesLegendAxes, } from "./components/series-chart.js";
|
|
14
6
|
export type { ChartHeight } from "./components/chart-height.js";
|
|
15
7
|
export * from "./components/chart-zoom.js";
|
|
16
8
|
export { SharedXZoom } from "./components/chart-zoom-core.js";
|
|
17
|
-
export
|
|
18
|
-
export {
|
|
19
|
-
/** @deprecated 0.52 — `strokeDash`, which picks a series' pattern from it (API-4); removed in 0.53 */
|
|
20
|
-
STROKE_PATTERNS, } from "./components/toggle-legend.js";
|
|
9
|
+
export { strokeDash, STEP_DASH, toggleHidden, ToggleLegend, StaticLegend, LegendColumn, LegendGroup, type LegendEntry, type ToggleLegendProps, type StaticLegendProps, } from "./components/toggle-legend.js";
|
|
21
10
|
export * from "./components/facing-pair.js";
|
|
22
11
|
export * from "./components/series-chart-labels.js";
|
|
23
12
|
export * from "./components/pie-chart.js";
|
package/dist/chart.js
CHANGED
|
@@ -1,31 +1,84 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
export * from "./components/chart.js";
|
|
3
|
-
export * from "./theme/chart-palette.js";
|
|
4
3
|
import {
|
|
5
|
-
|
|
6
|
-
|
|
4
|
+
PALETTE_SIZE,
|
|
5
|
+
CVD_ORDER,
|
|
6
|
+
paletteFor,
|
|
7
|
+
CHART_COLORS,
|
|
8
|
+
CHART_ROLES,
|
|
9
|
+
CHART_MUTED_HEX,
|
|
10
|
+
chartEdge,
|
|
11
|
+
PALETTE_HEX,
|
|
12
|
+
HEATMAP_HEX,
|
|
13
|
+
lerpHex,
|
|
14
|
+
textOn
|
|
7
15
|
} from "./theme/chart-palette.js";
|
|
8
16
|
export * from "./components/treemap.js";
|
|
9
17
|
export * from "./components/treemap-labels.js";
|
|
10
|
-
export * from "./components/series-chart.js";
|
|
11
18
|
import {
|
|
12
|
-
|
|
19
|
+
AXIS_TICK_WIDTH,
|
|
20
|
+
AXIS_TITLE_STRIP,
|
|
21
|
+
anchoredBand,
|
|
22
|
+
axisBandWidth,
|
|
23
|
+
mergeSeries,
|
|
24
|
+
oneAxis,
|
|
25
|
+
padBand,
|
|
26
|
+
paddedDomain,
|
|
27
|
+
seriesKey,
|
|
28
|
+
soleSeriesColor,
|
|
29
|
+
visibleSeries,
|
|
30
|
+
seriesLegendEntries,
|
|
31
|
+
StaticSeriesChart,
|
|
32
|
+
SeriesChart
|
|
13
33
|
} from "./components/series-chart.js";
|
|
14
34
|
export * from "./components/chart-zoom.js";
|
|
15
35
|
import { SharedXZoom } from "./components/chart-zoom-core.js";
|
|
16
|
-
export * from "./components/toggle-legend.js";
|
|
17
36
|
import {
|
|
18
|
-
|
|
37
|
+
strokeDash,
|
|
38
|
+
STEP_DASH,
|
|
39
|
+
toggleHidden,
|
|
40
|
+
ToggleLegend,
|
|
41
|
+
StaticLegend,
|
|
42
|
+
LegendColumn,
|
|
43
|
+
LegendGroup
|
|
19
44
|
} from "./components/toggle-legend.js";
|
|
20
45
|
export * from "./components/facing-pair.js";
|
|
21
46
|
export * from "./components/series-chart-labels.js";
|
|
22
47
|
export * from "./components/pie-chart.js";
|
|
23
48
|
export * from "./components/pie-chart-labels.js";
|
|
24
49
|
export {
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
50
|
+
AXIS_TICK_WIDTH,
|
|
51
|
+
AXIS_TITLE_STRIP,
|
|
52
|
+
CHART_COLORS,
|
|
53
|
+
CHART_MUTED_HEX,
|
|
54
|
+
CHART_ROLES,
|
|
55
|
+
CVD_ORDER,
|
|
56
|
+
HEATMAP_HEX,
|
|
57
|
+
LegendColumn,
|
|
58
|
+
LegendGroup,
|
|
59
|
+
PALETTE_HEX,
|
|
60
|
+
PALETTE_SIZE,
|
|
61
|
+
STEP_DASH,
|
|
62
|
+
SeriesChart,
|
|
28
63
|
SharedXZoom,
|
|
29
|
-
|
|
64
|
+
StaticLegend,
|
|
65
|
+
StaticSeriesChart,
|
|
66
|
+
ToggleLegend,
|
|
67
|
+
anchoredBand,
|
|
68
|
+
axisBandWidth,
|
|
69
|
+
chartEdge,
|
|
70
|
+
lerpHex,
|
|
71
|
+
mergeSeries,
|
|
72
|
+
oneAxis,
|
|
73
|
+
padBand,
|
|
74
|
+
paddedDomain,
|
|
75
|
+
paletteFor,
|
|
76
|
+
seriesKey,
|
|
77
|
+
seriesLegendEntries,
|
|
78
|
+
soleSeriesColor,
|
|
79
|
+
strokeDash,
|
|
80
|
+
textOn,
|
|
81
|
+
toggleHidden,
|
|
82
|
+
visibleSeries
|
|
30
83
|
};
|
|
31
84
|
//# sourceMappingURL=chart.js.map
|
package/dist/chart.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/chart.ts"],"sourcesContent":["// `@eifi1/ui-kit/chart` — the chart shell and its colour system.\n//\n// A re-slicing of what the main barrel already exports, not a new API. It exists so a\n// consumer can reach the chart kit WITHOUT the barrel: `import(\"@eifi1/ui-kit/chart\")`\n// is a chunk of its own (50.4 KB min, 19.9 KB gzip at 0.51.6, refactor audit 2026-10-30\n// MOD-7; it was 31KB when this was written), where the same lazy import through the barrel\n// pulls the barrel, because a dynamic import of it cannot be tree-shaken down to one member.\n//\n// It is about chunk size, not about the peer: `recharts` is a REQUIRED peer (since 0.5.0),\n// because the main barrel re-exports everything here too, so `import { Button } from\n// \"@eifi1/ui-kit\"` reaches recharts as well (packaging-contract.test.ts asserts it).\nexport * from \"./components/chart\";\
|
|
1
|
+
{"version":3,"sources":["../src/chart.ts"],"sourcesContent":["// `@eifi1/ui-kit/chart` — the chart shell and its colour system.\n//\n// A re-slicing of what the main barrel already exports, not a new API. It exists so a\n// consumer can reach the chart kit WITHOUT the barrel: `import(\"@eifi1/ui-kit/chart\")`\n// is a chunk of its own (50.4 KB min, 19.9 KB gzip at 0.51.6, refactor audit 2026-10-30\n// MOD-7; it was 31KB when this was written), where the same lazy import through the barrel\n// pulls the barrel, because a dynamic import of it cannot be tree-shaken down to one member.\n//\n// It is about chunk size, not about the peer: `recharts` is a REQUIRED peer (since 0.5.0),\n// because the main barrel re-exports everything here too, so `import { Button } from\n// \"@eifi1/ui-kit\"` reaches recharts as well (packaging-contract.test.ts asserts it).\nexport * from \"./components/chart\";\n// Named, not `*`, for chart-palette, series-chart and toggle-legend: 0.53 took the slice's\n// share of the 55 names no app reads off the surface (deprecated in 0.52, refactor audit\n// 2026-10-30, API-4) — `EDGE_MIN_CONTRAST`, `resolveTooltipPlacement`, `STROKE_PATTERNS`,\n// which their modules keep for the kit's own use.\nexport {\n PALETTE_SIZE,\n CVD_ORDER,\n paletteFor,\n CHART_COLORS,\n CHART_ROLES,\n CHART_MUTED_HEX,\n chartEdge,\n PALETTE_HEX,\n HEATMAP_HEX,\n lerpHex,\n textOn,\n type PaletteForOptions,\n type HeatStops,\n} from \"./theme/chart-palette\";\n\n// The tile chart (treemap) the apps share — moved here from keksdose so every consumer\n// derives it from the package rather than from a copy. 0.52: its `treemap` label namespace.\nexport * from \"./components/treemap\";\nexport * from \"./components/treemap-labels\";\n\n// lenkbank's zoomable series chart, its synced x-zoom, the toggle legend and the\n// mirrored-pair axis geometry — moved here so the apps derive them from the package.\nexport {\n AXIS_TICK_WIDTH,\n AXIS_TITLE_STRIP,\n anchoredBand,\n axisBandWidth,\n mergeSeries,\n oneAxis,\n padBand,\n paddedDomain,\n seriesKey,\n soleSeriesColor,\n visibleSeries,\n seriesLegendEntries,\n StaticSeriesChart,\n SeriesChart,\n type TimeTickUnit,\n type SeriesChartAxisBudget,\n type SeriesSource,\n type SeriesChartSeries,\n type SeriesChartType,\n type SeriesChartPoint,\n type SeriesChartRow,\n type SeriesChartXValue,\n type SeriesChartTickValues,\n type SeriesChartAxis,\n type SeriesChartXTick,\n type SeriesChartX,\n type SeriesChartTooltipPlacement,\n type SeriesChartTooltip,\n type SeriesChartSpan,\n type SeriesChartTone,\n type SeriesChartReference,\n type SeriesChartMarker,\n type SeriesChartHit,\n type SeriesChartProps,\n type SeriesLegendAxes,\n} from \"./components/series-chart\";\n// 0.32.1 (keksdose's 0.32 report): the height every chart takes, for an app's wrappers.\nexport type { ChartHeight } from \"./components/chart-height\";\n// SharedXZoom and the zoom's types: the public face of ./chart-zoom-core, whose arithmetic\n// stays the kit's (refactor audit 2026-10, API-6b; off the surface in 0.35).\nexport * from \"./components/chart-zoom\";\n// The same binding, named from the module that holds it and the drag layer (recharts): the\n// packaging contract reads this entry's own imports to know which modules may import\n// recharts, so chart-zoom-core has to be one of them (MOD-6).\nexport { SharedXZoom } from \"./components/chart-zoom-core\";\nexport {\n strokeDash,\n STEP_DASH,\n toggleHidden,\n ToggleLegend,\n StaticLegend,\n LegendColumn,\n LegendGroup,\n type LegendEntry,\n type ToggleLegendProps,\n type StaticLegendProps,\n} from \"./components/toggle-legend\";\nexport * from \"./components/facing-pair\";\nexport * from \"./components/series-chart-labels\";\n\n// The part-to-whole chart (donut / pie) — moved here from kastlan's dashboard and reports.\nexport * from \"./components/pie-chart\";\nexport * from \"./components/pie-chart-labels\";\n"],"mappings":";AAWA,cAAc;AAKd;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAGK;AAIP,cAAc;AACd,cAAc;AAId;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAsBK;AAKP,cAAc;AAId,SAAS,mBAAmB;AAC5B;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAIK;AACP,cAAc;AACd,cAAc;AAGd,cAAc;AACd,cAAc;","names":[]}
|
|
@@ -109,7 +109,7 @@ export interface DatePickerBaseProps extends Omit<ComponentPropsWithoutRef<"div"
|
|
|
109
109
|
*
|
|
110
110
|
* Off by default: the caption becomes one more stop in the panel's tab order, which a
|
|
111
111
|
* 0.x minor does not spring on a form. Works in the popover and in
|
|
112
|
-
* `DateRangePicker`'s phone sheet alike, beside presets and
|
|
112
|
+
* `DateRangePicker`'s phone sheet alike, beside presets and with `apply`
|
|
113
113
|
* (a preset picked while the months are open goes back to the days, on its start).
|
|
114
114
|
*
|
|
115
115
|
* Why not a two-month panel (`months={2}`) instead, or beside it: for a twelve-month
|