@giddaa-housing/ui 3.3.0 → 3.5.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.
Files changed (134) hide show
  1. package/css/generated/shared.css +1 -1
  2. package/css/theme.css +20 -0
  3. package/dist/accordion.js +1 -1
  4. package/dist/activity-timeline.js +1 -1
  5. package/dist/alert.js +1 -1
  6. package/dist/auth-layout.js +1 -1
  7. package/dist/avatar.js +1 -1
  8. package/dist/badge.js +6 -1
  9. package/dist/bar-chart.js +1 -1
  10. package/dist/breadcrumb.js +1 -1
  11. package/dist/bullet-chart.js +1 -1
  12. package/dist/button-group.js +1 -1
  13. package/dist/button.js +1 -1
  14. package/dist/calendar.js +2 -2
  15. package/dist/call-card.js +1 -1
  16. package/dist/card.js +1 -1
  17. package/dist/carousel.js +2 -2
  18. package/dist/chart.js +1 -1
  19. package/dist/chat.js +1 -1
  20. package/dist/checkbox.js +1 -1
  21. package/dist/collapsible.js +1 -1
  22. package/dist/combobox-B7cKFJ9V.js +398 -0
  23. package/dist/combobox.d.ts +2 -2
  24. package/dist/combobox.js +1 -308
  25. package/dist/cta.js +1 -1
  26. package/dist/currency-selector.js +2 -2
  27. package/dist/data-table.d.ts +1 -1
  28. package/dist/data-table.js +1 -1
  29. package/dist/date-picker.d.ts +4 -3
  30. package/dist/date-picker.js +7 -17
  31. package/dist/details-card.js +1 -1
  32. package/dist/dialog.js +1 -1
  33. package/dist/dropdown-menu.js +1 -1
  34. package/dist/dropzone.js +1 -1
  35. package/dist/editorial-card.js +2 -4
  36. package/dist/empty-state.js +1 -1
  37. package/dist/field.js +1 -1
  38. package/dist/file-upload.js +1 -1
  39. package/dist/filter.js +1 -1
  40. package/dist/footer.js +1 -1
  41. package/dist/funnel-chart.js +1 -1
  42. package/dist/gallery-indicator.js +1 -1
  43. package/dist/icons-C9JXR5Xv.d.ts +111 -0
  44. package/dist/icons.d.ts +2 -93
  45. package/dist/icons.js +37 -1
  46. package/dist/image.d.ts +59 -0
  47. package/dist/image.js +53 -0
  48. package/dist/infinite-scroll.js +3 -29
  49. package/dist/infotip.js +1 -1
  50. package/dist/input-group.js +1 -1
  51. package/dist/input-otp.js +1 -1
  52. package/dist/input.js +1 -1
  53. package/dist/item.js +1 -1
  54. package/dist/kpi-card.js +1 -1
  55. package/dist/label.js +1 -1
  56. package/dist/line-chart.js +1 -1
  57. package/dist/list-item.js +1 -1
  58. package/dist/map-indicator.js +1 -1
  59. package/dist/match.js +1 -1
  60. package/dist/media-gallery-hero.js +6 -3
  61. package/dist/media-player.js +5 -13
  62. package/dist/message.js +2 -12
  63. package/dist/mobile-sidebar.js +1 -1
  64. package/dist/multi-step-form.js +2 -2
  65. package/dist/number-input.js +1 -1
  66. package/dist/page-header.js +1 -1
  67. package/dist/pagination.js +1 -1
  68. package/dist/password-input.js +1 -1
  69. package/dist/phone-input.js +11 -17
  70. package/dist/photo-gallery.js +6 -3
  71. package/dist/picker-CsnHtbe4.d.ts +14 -0
  72. package/dist/{picker-xSTzeYhc.js → picker-DmIiQKLJ.js} +2 -7
  73. package/dist/pie-chart.js +1 -1
  74. package/dist/popover-DSlPeH7x.d.ts +20 -0
  75. package/dist/popover.d.ts +1 -19
  76. package/dist/popover.js +1 -1
  77. package/dist/price-tag.d.ts +80 -12
  78. package/dist/price-tag.js +139 -70
  79. package/dist/progress.js +1 -1
  80. package/dist/property-listing-card.d.ts +63 -1
  81. package/dist/property-listing-card.js +97 -28
  82. package/dist/purchase-option-card.js +1 -1
  83. package/dist/radar-chart.js +1 -1
  84. package/dist/radio-group.js +1 -1
  85. package/dist/review-block.js +1 -1
  86. package/dist/rich-text-editor.js +1 -1
  87. package/dist/scatter-chart.js +1 -1
  88. package/dist/scroll-spy.d.ts +16 -1
  89. package/dist/scroll-spy.js +28 -5
  90. package/dist/search-input.js +1 -1
  91. package/dist/select.js +1 -1
  92. package/dist/separator.js +1 -1
  93. package/dist/sheet.js +1 -1
  94. package/dist/sidebar.js +3 -16
  95. package/dist/skeleton.js +1 -1
  96. package/dist/slider.js +1 -1
  97. package/dist/sonar.d.ts +51 -0
  98. package/dist/sonar.js +117 -0
  99. package/dist/sparkline.js +1 -1
  100. package/dist/stepper.js +1 -1
  101. package/dist/styles.css +85 -2
  102. package/dist/switch.js +1 -1
  103. package/dist/table-card.js +1 -1
  104. package/dist/table.js +1 -1
  105. package/dist/tabs.js +1 -1
  106. package/dist/tag.js +1 -1
  107. package/dist/testimonial-block.js +1 -1
  108. package/dist/textarea.js +1 -1
  109. package/dist/theme-switcher.js +1 -1
  110. package/dist/time-picker.d.ts +3 -2
  111. package/dist/time-picker.js +5 -7
  112. package/dist/toast.js +1 -1
  113. package/dist/tooltip.js +1 -1
  114. package/dist/top-navbar.js +1 -1
  115. package/dist/tree-map.js +1 -1
  116. package/dist/utils/cn.d.ts +5 -0
  117. package/dist/utils/cn.js +33 -0
  118. package/dist/utils/format.d.ts +45 -0
  119. package/dist/utils/format.js +70 -0
  120. package/dist/utils/phone.d.ts +98 -0
  121. package/dist/utils/phone.js +93 -0
  122. package/dist/utils/use-intersection.d.ts +9 -0
  123. package/dist/utils/use-intersection.js +29 -0
  124. package/dist/utils/use-is-mobile.d.ts +5 -0
  125. package/dist/utils/use-is-mobile.js +16 -0
  126. package/dist/utils/use-reduced-motion.d.ts +5 -0
  127. package/dist/{use-reduced-motion-BDvOK14x.js → utils/use-reduced-motion.js} +2 -2
  128. package/dist/utils/use-uncontrolled.d.ts +11 -0
  129. package/dist/{use-uncontrolled-CLfM1XbB.js → utils/use-uncontrolled.js} +2 -2
  130. package/dist/video-player-dialog.d.ts +12 -2
  131. package/dist/video-player-dialog.js +47 -7
  132. package/dist/waterfall-chart.js +1 -1
  133. package/package.json +13 -1
  134. package/dist/cn-BI_4DMBf.js +0 -23
@@ -1,8 +1,8 @@
1
1
  "use client";
2
2
  import { Check } from "./icons.js";
3
- import { t as cn } from "./cn-BI_4DMBf.js";
4
- import { t as useUncontrolled } from "./use-uncontrolled-CLfM1XbB.js";
3
+ import { cn } from "./utils/cn.js";
5
4
  import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "./select.js";
5
+ import { useUncontrolled } from "./utils/use-uncontrolled.js";
6
6
  import { Progress, ProgressIndicator, ProgressTrack } from "./progress.js";
7
7
  import { jsx, jsxs } from "react/jsx-runtime";
8
8
  import * as React from "react";
@@ -1,6 +1,6 @@
1
1
  "use client";
2
2
  import { Minus, Plus } from "./icons.js";
3
- import { t as cn } from "./cn-BI_4DMBf.js";
3
+ import { cn } from "./utils/cn.js";
4
4
  import { SizeProvider, useComponentSize } from "./size-context.js";
5
5
  import { InputGroup, InputGroupAddon, InputGroupButton, InputGroupInput } from "./input-group.js";
6
6
  import { jsx, jsxs } from "react/jsx-runtime";
@@ -1,4 +1,4 @@
1
- import { t as cn } from "./cn-BI_4DMBf.js";
1
+ import { cn } from "./utils/cn.js";
2
2
  import { jsx } from "react/jsx-runtime";
3
3
  //#region src/page-header.tsx
4
4
  function DashboardHeader({ hideBorder = false, className, ...props }) {
@@ -1,5 +1,5 @@
1
1
  import { ChevronLeft, ChevronRight, MoreHorizontal } from "./icons.js";
2
- import { t as cn } from "./cn-BI_4DMBf.js";
2
+ import { cn } from "./utils/cn.js";
3
3
  import { Button, ButtonLink } from "./button.js";
4
4
  import { jsx, jsxs } from "react/jsx-runtime";
5
5
  import { cva } from "class-variance-authority";
@@ -1,6 +1,6 @@
1
1
  "use client";
2
2
  import { Eye, EyeOff } from "./icons.js";
3
- import { t as cn } from "./cn-BI_4DMBf.js";
3
+ import { cn } from "./utils/cn.js";
4
4
  import { InputGroup, InputGroupAddon, InputGroupButton, InputGroupInput } from "./input-group.js";
5
5
  import { jsx, jsxs } from "react/jsx-runtime";
6
6
  import * as React from "react";
@@ -1,21 +1,17 @@
1
1
  "use client";
2
2
  import { Check, ChevronDown, Search } from "./icons.js";
3
- import { t as cn } from "./cn-BI_4DMBf.js";
3
+ import { cn } from "./utils/cn.js";
4
4
  import { useComponentSize } from "./size-context.js";
5
5
  import { Separator } from "./separator.js";
6
6
  import { InputGroup, InputGroupAddon, InputGroupInput } from "./input-group.js";
7
7
  import { Popover, PopoverContent, PopoverTrigger } from "./popover.js";
8
+ import { getPhoneCountries } from "./utils/phone.js";
8
9
  import { jsx, jsxs } from "react/jsx-runtime";
9
10
  import * as React from "react";
10
- import { AsYouType, getCountries, getCountryCallingCode, parsePhoneNumber } from "libphonenumber-js";
11
+ import { AsYouType, getCountryCallingCode, parsePhoneNumberFromString } from "libphonenumber-js";
11
12
  import flags from "react-phone-number-input/flags";
12
13
  //#region src/phone-input.tsx
13
- const regionNames = typeof Intl !== "undefined" && "DisplayNames" in Intl ? new Intl.DisplayNames(["en"], { type: "region" }) : void 0;
14
- const COUNTRIES = getCountries().map((code) => ({
15
- code,
16
- name: regionNames?.of(code) ?? code,
17
- calling: getCountryCallingCode(code)
18
- })).sort((a, b) => a.name.localeCompare(b.name));
14
+ const COUNTRIES = getPhoneCountries();
19
15
  function getNationalDigits(value) {
20
16
  return value.replace(/\D/g, "");
21
17
  }
@@ -29,13 +25,11 @@ function toE164(digits, country) {
29
25
  }
30
26
  function parseValue(value) {
31
27
  if (!value) return { digits: "" };
32
- try {
33
- const parsed = parsePhoneNumber(value);
34
- if (parsed?.nationalNumber) return {
35
- country: parsed.country,
36
- digits: parsed.nationalNumber
37
- };
38
- } catch {}
28
+ const parsed = parsePhoneNumberFromString(value);
29
+ if (parsed?.nationalNumber) return {
30
+ country: parsed.country,
31
+ digits: parsed.nationalNumber
32
+ };
39
33
  return { digits: "" };
40
34
  }
41
35
  const flagSizeVariants = {
@@ -107,7 +101,7 @@ function CountryList({ items, country, onSelect }) {
107
101
  }),
108
102
  /* @__PURE__ */ jsxs("span", {
109
103
  className: "shrink-0 text-fg-secondary",
110
- children: ["+", entry.calling]
104
+ children: ["+", entry.callingCode]
111
105
  }),
112
106
  selected && /* @__PURE__ */ jsx(Check, { className: "size-4 shrink-0 text-fg-brand" })
113
107
  ]
@@ -124,7 +118,7 @@ function CountrySelect({ country, size, disabled, onSelect, finalFocusRef }) {
124
118
  const q = query.trim().toLowerCase();
125
119
  if (!q) return COUNTRIES;
126
120
  const normalized = q.replace(/^\+/, "");
127
- return COUNTRIES.filter((entry) => entry.name.toLowerCase().includes(q) || entry.calling.includes(normalized) || entry.code.toLowerCase().includes(q));
121
+ return COUNTRIES.filter((entry) => entry.name.toLowerCase().includes(q) || entry.callingCode.includes(normalized) || entry.code.toLowerCase().includes(q));
128
122
  }, [query]);
129
123
  function handleOpenChange(next) {
130
124
  setOpen(next);
@@ -1,8 +1,9 @@
1
1
  "use client";
2
2
  import { ChevronLeft, ChevronRight, Fullscreen, X } from "./icons.js";
3
- import { t as cn } from "./cn-BI_4DMBf.js";
3
+ import { cn } from "./utils/cn.js";
4
4
  import { Button } from "./button.js";
5
5
  import { Carousel, CarouselContent, CarouselItem } from "./carousel.js";
6
+ import { Image } from "./image.js";
6
7
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
7
8
  import * as React from "react";
8
9
  import { Dialog } from "@base-ui/react/dialog";
@@ -60,7 +61,8 @@ function GalleryFrame({ image, fit = "cover", className }) {
60
61
  return /* @__PURE__ */ jsx("div", {
61
62
  "data-slot": "photo-gallery-frame",
62
63
  className: cn("relative flex size-full items-center justify-center overflow-hidden", className),
63
- children: image?.src ? /* @__PURE__ */ jsx("img", {
64
+ children: image?.src ? /* @__PURE__ */ jsx(Image, {
65
+ slot: "photo-gallery-image",
64
66
  src: image.src,
65
67
  alt: image.alt ?? "Property image",
66
68
  className: cn(fit === "contain" ? "max-h-full max-w-full rounded-lg object-contain" : "size-full object-cover"),
@@ -168,7 +170,8 @@ function GalleryThumbnails({ images, selectedIndex, onSelect, colsClassName, cla
168
170
  onClick: () => onSelect(index),
169
171
  style: { width: "calc((100% - (var(--gallery-cols) - 1) * var(--gallery-gap)) / var(--gallery-cols))" },
170
172
  className: cn("relative aspect-4/3 shrink-0 overflow-hidden rounded-lg border-2 bg-surface-raised transition-[border-color,opacity] outline-none focus-visible:border-line-focus focus-visible:shadow-[0px_0px_0px_2px_var(--color-line-focus)]", isActive ? "border-line-focus" : "border-transparent opacity-80 hover:opacity-100"),
171
- children: image.src ? /* @__PURE__ */ jsx("img", {
173
+ children: image.src ? /* @__PURE__ */ jsx(Image, {
174
+ slot: "photo-gallery-thumbnail-image",
172
175
  src: image.src,
173
176
  alt: image.alt ?? `Property image ${index + 1}`,
174
177
  className: "size-full object-cover",
@@ -0,0 +1,14 @@
1
+ import "./input-size-context-C-SFx9sx.js";
2
+ import { r as PopoverContent } from "./popover-DSlPeH7x.js";
3
+ import "./icons-C9JXR5Xv.js";
4
+ import * as React from "react";
5
+ //#region src/lib/picker.d.ts
6
+ /**
7
+ * Where a picker's panel goes. Every one of these is a preference the
8
+ * positioner is free to overrule when the panel would otherwise overflow —
9
+ * which is the point, a calendar being tall enough that the room below a
10
+ * trigger often isn't the room it needs.
11
+ */
12
+ type PickerPlacementProps = Pick<React.ComponentProps<typeof PopoverContent>, "side" | "sideOffset" | "align" | "alignOffset" | "collisionAvoidance">;
13
+ //#endregion
14
+ export { PickerPlacementProps as t };
@@ -1,15 +1,10 @@
1
- import { t as cn } from "./cn-BI_4DMBf.js";
1
+ import { cn } from "./utils/cn.js";
2
2
  import { Button } from "./button.js";
3
3
  import { useInputComponentSize } from "./input-size-context.js";
4
4
  import { PopoverTrigger } from "./popover.js";
5
5
  import { jsx, jsxs } from "react/jsx-runtime";
6
6
  import { cva } from "class-variance-authority";
7
7
  //#region src/lib/picker.tsx
8
- const PICKER_COLLISION_AVOIDANCE = {
9
- side: "flip",
10
- align: "shift",
11
- fallbackAxisSide: "none"
12
- };
13
8
  const pickerTriggerVariants = cva("flex w-full items-center justify-between rounded-full border transition-[border-color,box-shadow] outline-none", {
14
9
  variants: { size: {
15
10
  sm: "h-8 px-3 text-gdt-xs [&_svg]:size-3.5",
@@ -47,4 +42,4 @@ function PickerFooter({ secondaryLabel, onSecondary, onApply }) {
47
42
  });
48
43
  }
49
44
  //#endregion
50
- export { PickerFooter as n, PickerTrigger as r, PICKER_COLLISION_AVOIDANCE as t };
45
+ export { PickerTrigger as n, PickerFooter as t };
package/dist/pie-chart.js CHANGED
@@ -1,5 +1,5 @@
1
1
  "use client";
2
- import { t as cn } from "./cn-BI_4DMBf.js";
2
+ import { cn } from "./utils/cn.js";
3
3
  import { SizeProvider, useComponentSize } from "./size-context.js";
4
4
  import { ChartContainer } from "./chart.js";
5
5
  import { jsx, jsxs } from "react/jsx-runtime";
@@ -0,0 +1,20 @@
1
+ import { t as ComponentSize } from "./size-context-BX6kAdVj.js";
2
+ import * as React from "react";
3
+ import { Popover } from "@base-ui/react/popover";
4
+ //#region src/popover.d.ts
5
+ declare function Popover$1({ ...props }: Popover.Root.Props): React.JSX.Element;
6
+ declare function PopoverTrigger({ ...props }: Popover.Trigger.Props): React.JSX.Element;
7
+ type PopoverContentProps = Popover.Popup.Props & Pick<Popover.Positioner.Props, "align" | "alignOffset" | "collisionAvoidance" | "side" | "sideOffset"> & {
8
+ showArrow?: boolean;
9
+ size?: ComponentSize;
10
+ };
11
+ declare function PopoverContent({ className, align, alignOffset, collisionAvoidance, side, sideOffset, showArrow, size, children, ...props }: PopoverContentProps): React.JSX.Element;
12
+ declare function PopoverHeader({ className, border, ...props }: React.ComponentProps<"div"> & {
13
+ border?: boolean;
14
+ }): React.JSX.Element;
15
+ declare function PopoverBody({ className, ...props }: React.ComponentProps<"div">): React.JSX.Element;
16
+ declare function PopoverFooter({ className, ...props }: React.ComponentProps<"div">): React.JSX.Element;
17
+ declare function PopoverTitle({ className, ...props }: Popover.Title.Props): React.JSX.Element;
18
+ declare function PopoverDescription({ className, ...props }: Popover.Description.Props): React.JSX.Element;
19
+ //#endregion
20
+ export { PopoverFooter as a, PopoverTrigger as c, PopoverDescription as i, PopoverBody as n, PopoverHeader as o, PopoverContent as r, PopoverTitle as s, Popover$1 as t };
package/dist/popover.d.ts CHANGED
@@ -1,20 +1,2 @@
1
- import { t as ComponentSize } from "./size-context-BX6kAdVj.js";
2
- import * as React from "react";
3
- import { Popover as Popover$1 } from "@base-ui/react/popover";
4
- //#region src/popover.d.ts
5
- declare function Popover({ ...props }: Popover$1.Root.Props): React.JSX.Element;
6
- declare function PopoverTrigger({ ...props }: Popover$1.Trigger.Props): React.JSX.Element;
7
- type PopoverContentProps = Popover$1.Popup.Props & Pick<Popover$1.Positioner.Props, "align" | "alignOffset" | "collisionAvoidance" | "side" | "sideOffset"> & {
8
- showArrow?: boolean;
9
- size?: ComponentSize;
10
- };
11
- declare function PopoverContent({ className, align, alignOffset, collisionAvoidance, side, sideOffset, showArrow, size, children, ...props }: PopoverContentProps): React.JSX.Element;
12
- declare function PopoverHeader({ className, border, ...props }: React.ComponentProps<"div"> & {
13
- border?: boolean;
14
- }): React.JSX.Element;
15
- declare function PopoverBody({ className, ...props }: React.ComponentProps<"div">): React.JSX.Element;
16
- declare function PopoverFooter({ className, ...props }: React.ComponentProps<"div">): React.JSX.Element;
17
- declare function PopoverTitle({ className, ...props }: Popover$1.Title.Props): React.JSX.Element;
18
- declare function PopoverDescription({ className, ...props }: Popover$1.Description.Props): React.JSX.Element;
19
- //#endregion
1
+ import { a as PopoverFooter, c as PopoverTrigger, i as PopoverDescription, n as PopoverBody, o as PopoverHeader, r as PopoverContent, s as PopoverTitle, t as Popover } from "./popover-DSlPeH7x.js";
20
2
  export { Popover, PopoverBody, PopoverContent, PopoverDescription, PopoverFooter, PopoverHeader, PopoverTitle, PopoverTrigger };
package/dist/popover.js CHANGED
@@ -1,5 +1,5 @@
1
1
  "use client";
2
- import { t as cn } from "./cn-BI_4DMBf.js";
2
+ import { cn } from "./utils/cn.js";
3
3
  import { SizeProvider } from "./size-context.js";
4
4
  import { jsx, jsxs } from "react/jsx-runtime";
5
5
  import { Popover as Popover$1 } from "@base-ui/react/popover";
@@ -3,32 +3,100 @@ import * as React from "react";
3
3
  //#region src/price-tag.d.ts
4
4
  declare const priceTagVariants: (props?: ({
5
5
  size?: "lg" | "md" | "sm" | "xs" | "xxs" | null | undefined;
6
+ hasDescription?: boolean | null | undefined;
6
7
  hasMonthlyPayment?: boolean | null | undefined;
7
8
  } & import("class-variance-authority/types").ClassProp) | undefined) => string;
8
9
  type PriceTagSize = NonNullable<VariantProps<typeof priceTagVariants>["size"]>;
10
+ /**
11
+ * A currency in the dropdown.
12
+ *
13
+ * `currency`, `value` and `description` are all PriceTag needs, and none of
14
+ * them are interpreted — `description` in particular is printed as given, so
15
+ * the wording is yours. The rest are legacy and go in the next major.
16
+ */
9
17
  type PriceTagCurrencyOption = {
10
- code: string;
11
- amount: string;
18
+ /** Shown at the leading edge, and matched against `selectedCurrency`. */
19
+ currency?: string;
20
+ /** The price, exactly as it should read. */
21
+ value?: React.ReactNode;
22
+ /** A second line under the price. Your words, printed as given. */
23
+ description?: React.ReactNode;
24
+ /** @deprecated Use `currency`. Removed in the next major. */
25
+ code?: string;
26
+ /** @deprecated Use `value`. Removed in the next major. */
27
+ amount?: string;
28
+ /** @deprecated Format before you pass it. Removed in the next major. */
12
29
  shortAmount?: string;
30
+ /** @deprecated Use `description`. Removed in the next major. */
13
31
  monthlyPayment?: string;
32
+ /** @deprecated Use `description`. Removed in the next major. */
14
33
  monthlyPaymentLabel?: string;
15
34
  };
35
+ /**
36
+ * A currency option after the legacy fields have been folded in — what
37
+ * `onCurrencyChange` and `renderMenuOption` receive. The original object is
38
+ * spread in, so code still reading `code` or `amount` off it keeps working.
39
+ *
40
+ * Generic over your own option type, which `PriceTag` infers from `currencies`.
41
+ * Anything extra you hang on an option — a rate, an id, a flag — arrives here
42
+ * typed, so `renderMenuOption` can use it without a cast.
43
+ */
44
+ type PriceTagMenuOption<TOption extends PriceTagCurrencyOption = PriceTagCurrencyOption> = TOption & {
45
+ currency: string;
46
+ value: React.ReactNode;
47
+ description?: React.ReactNode;
48
+ /** Whether this is the row `selectedCurrency` points at. */
49
+ selected: boolean;
50
+ };
51
+ /** @deprecated Format before you pass it. Removed in the next major. */
16
52
  type PriceTagFormat = "full" | "shortened";
17
- type PriceTagProps = Omit<React.ComponentProps<"button">, "children"> & {
18
- amount: string;
19
- shortAmount?: string;
20
- format?: PriceTagFormat;
53
+ type PriceTagProps<TOption extends PriceTagCurrencyOption = PriceTagCurrencyOption> = Omit<React.ComponentProps<"button">, "children" | "value"> & {
54
+ /** The price, exactly as it should read. */
55
+ value?: React.ReactNode;
56
+ /** A second line under the price. Your words, printed as given. */
57
+ description?: React.ReactNode;
21
58
  size?: PriceTagSize;
22
- monthlyPayment?: string;
23
- monthlyPaymentLabel?: string;
24
- currencies?: PriceTagCurrencyOption[];
59
+ /**
60
+ * Providing this turns the tag into a dropdown trigger. Options carry
61
+ * whatever else you need — `TOption` is inferred from here, and the
62
+ * callbacks below receive it intact.
63
+ */
64
+ currencies?: TOption[];
25
65
  selectedCurrency?: string;
26
- onCurrencyChange?: (currency: PriceTagCurrencyOption) => void;
66
+ onCurrencyChange?: (option: PriceTagMenuOption<NoInfer<TOption>>) => void;
67
+ /**
68
+ * Replace a row's contents. PriceTag keeps the button, its selected state
69
+ * and its keyboard behaviour, so a custom row can't quietly lose them.
70
+ */
71
+ renderMenuOption?: (option: PriceTagMenuOption<NoInfer<TOption>>) => React.ReactNode;
27
72
  open?: boolean;
28
73
  defaultOpen?: boolean;
29
74
  onOpenChange?: (open: boolean) => void;
30
75
  popoverClassName?: string;
76
+ /** @deprecated Use `value`. Removed in the next major. */
77
+ amount?: string;
78
+ /** @deprecated Format before you pass it. Removed in the next major. */
79
+ shortAmount?: string;
80
+ /** @deprecated Format before you pass it. Removed in the next major. */
81
+ format?: PriceTagFormat;
82
+ /** @deprecated Use `description`. Removed in the next major. */
83
+ monthlyPayment?: string;
84
+ /** @deprecated Use `description`. Removed in the next major. */
85
+ monthlyPaymentLabel?: string;
31
86
  };
32
- declare function PriceTag({ amount, shortAmount, format, size, monthlyPayment, monthlyPaymentLabel, currencies, selectedCurrency, onCurrencyChange, open, defaultOpen, onOpenChange, className, popoverClassName, type, ...props }: PriceTagProps): React.JSX.Element;
87
+ /**
88
+ * Rounded-box price display, optionally a trigger for a currency dropdown.
89
+ *
90
+ * Display only: it prints `value` and `description` as given and never
91
+ * composes a sentence, picks a format, or decides that a line is too small to
92
+ * be worth showing. Wording, currency conversion and number formatting all
93
+ * belong to the app, which is the only place that knows the locale, the
94
+ * product's voice, and what the second line is actually for.
95
+ *
96
+ * The legacy `amount` / `shortAmount` / `format` / `monthlyPayment` props still
97
+ * work and keep their old behaviour exactly, including the size threshold that
98
+ * hid the monthly line below `md`. They go in the next major.
99
+ */
100
+ declare function PriceTag<TOption extends PriceTagCurrencyOption = PriceTagCurrencyOption>({ value, description, size, currencies, selectedCurrency, onCurrencyChange, renderMenuOption, open, defaultOpen, onOpenChange, className, popoverClassName, type, amount, shortAmount, format, monthlyPayment, monthlyPaymentLabel, ...props }: PriceTagProps<TOption>): React.JSX.Element;
33
101
  //#endregion
34
- export { PriceTag, type PriceTagCurrencyOption, type PriceTagFormat, type PriceTagProps, type PriceTagSize, priceTagVariants };
102
+ export { PriceTag, type PriceTagCurrencyOption, type PriceTagFormat, type PriceTagMenuOption, type PriceTagProps, type PriceTagSize, priceTagVariants };
package/dist/price-tag.js CHANGED
@@ -1,19 +1,25 @@
1
1
  "use client";
2
2
  import { Check, ChevronDown } from "./icons.js";
3
- import { t as cn } from "./cn-BI_4DMBf.js";
4
- import { jsx, jsxs } from "react/jsx-runtime";
3
+ import { cn } from "./utils/cn.js";
4
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
5
5
  import { cva } from "class-variance-authority";
6
6
  import { Popover } from "@base-ui/react/popover";
7
7
  //#region src/price-tag.tsx
8
- const priceTagVariants = cva("group/price-tag inline-flex w-fit shrink-0 items-center justify-center rounded-4xl bg-surface-brand font-bold leading-none whitespace-nowrap text-fg-on-brand shadow-none transition-[background-color,box-shadow] outline-none data-[interactive=true]:cursor-pointer data-[interactive=true]:hover:bg-action-primary-hover data-[interactive=true]:focus-visible:shadow-[0px_0px_0px_2px_var(--color-line-focus)] data-[interactive=true]:active:bg-action-primary-pressed", {
8
+ const priceTagVariants = cva("group/price-tag inline-flex w-fit shrink-0 items-center justify-center bg-surface-brand font-bold whitespace-nowrap text-fg-on-brand shadow-none transition-[background-color,box-shadow] outline-none data-[interactive=true]:cursor-pointer data-[interactive=true]:hover:bg-action-primary-hover data-[interactive=true]:focus-visible:shadow-[0px_0px_0px_2px_var(--color-line-focus)] data-[interactive=true]:active:bg-action-primary-pressed", {
9
9
  variants: {
10
10
  size: {
11
- xxs: "min-h-7 gap-1 px-2.5 py-0.75 text-gdt-subtext [&>svg]:size-3",
12
- xs: "min-h-7.5 gap-1 px-3 py-1 text-gdt-xs [&>svg]:size-3.5",
13
- sm: "min-h-9 gap-1.5 px-4 py-1.5 text-gdt-sm [&>svg]:size-3.5",
14
- md: "min-h-10.5 gap-1.5 px-5 py-2 text-gdt-md [&>svg]:size-4",
15
- lg: "min-h-13 gap-2 px-7 py-3 text-gdt-lg [&>svg]:size-4.5"
11
+ xxs: "min-h-7 gap-1 rounded-lg px-2.5 py-0.75 text-gdt-subtext leading-none [&_svg]:size-3",
12
+ xs: "min-h-7.5 gap-1 rounded-lg px-3 py-1 text-gdt-xs leading-none [&_svg]:size-3.5",
13
+ sm: "min-h-9 gap-1.5 rounded-xl px-4 py-1.5 text-gdt-sm leading-none [&_svg]:size-3.5",
14
+ md: "min-h-10.5 gap-1.5 rounded-xl px-5 py-2 text-gdt-md leading-none [&_svg]:size-4",
15
+ lg: "min-h-13 gap-2 rounded-2xl px-7 py-3 text-gdt-lg leading-none [&_svg]:size-4.5"
16
16
  },
17
+ /** Two lines sit top-aligned against the chevron rather than centred. */
18
+ hasDescription: {
19
+ true: "items-start",
20
+ false: ""
21
+ },
22
+ /** @deprecated Use `hasDescription`. Removed in the next major. */
17
23
  hasMonthlyPayment: {
18
24
  true: "items-start",
19
25
  false: ""
@@ -21,95 +27,158 @@ const priceTagVariants = cva("group/price-tag inline-flex w-fit shrink-0 items-c
21
27
  },
22
28
  defaultVariants: {
23
29
  size: "md",
24
- hasMonthlyPayment: false
30
+ hasDescription: false
25
31
  }
26
32
  });
33
+ /** The space between the price and its description, 4px through 12px. */
34
+ const lineGapClassName = {
35
+ xxs: "gap-1",
36
+ xs: "gap-1",
37
+ sm: "gap-1.5",
38
+ md: "gap-2",
39
+ lg: "gap-3"
40
+ };
41
+ /**
42
+ * The least space the chevron will sit at from the price. It is usually pushed
43
+ * further out than this, to the trailing edge of the wider description.
44
+ */
45
+ const chevronGapClassName = {
46
+ xxs: "gap-2",
47
+ xs: "gap-2",
48
+ sm: "gap-3",
49
+ md: "gap-3",
50
+ lg: "gap-4"
51
+ };
52
+ /**
53
+ * The description is quieter than the price, and quietest at the largest size.
54
+ * `leading-none` trails the text size here for the same reason it does above.
55
+ */
56
+ const descriptionSizeClassName = {
57
+ xxs: "text-gdt-subtext leading-none",
58
+ xs: "text-gdt-subtext leading-none",
59
+ sm: "text-gdt-subtext leading-none",
60
+ md: "text-gdt-subtext leading-none",
61
+ lg: "text-gdt-xs leading-none"
62
+ };
27
63
  function getDisplayAmount({ amount, shortAmount, format }) {
28
64
  return format === "shortened" && shortAmount ? shortAmount : amount;
29
65
  }
30
- function PriceTag({ amount, shortAmount, format = "full", size = "md", monthlyPayment, monthlyPaymentLabel = "Low as", currencies, selectedCurrency, onCurrencyChange, open, defaultOpen, onOpenChange, className, popoverClassName, type = "button", ...props }) {
66
+ /**
67
+ * Folds the legacy fields into the current three. New fields win outright, so
68
+ * an option can be migrated one at a time.
69
+ */
70
+ function resolveMenuOption(option, format, selectedCurrency) {
71
+ const currency = option.currency ?? option.code ?? "";
72
+ const value = option.value ?? getDisplayAmount({
73
+ amount: option.amount,
74
+ shortAmount: option.shortAmount,
75
+ format
76
+ });
77
+ const description = option.description ?? (option.monthlyPayment ? `${option.monthlyPaymentLabel ?? "As low as"} ${option.monthlyPayment} per month` : void 0);
78
+ return {
79
+ ...option,
80
+ currency,
81
+ value,
82
+ description,
83
+ selected: currency === selectedCurrency
84
+ };
85
+ }
86
+ function DefaultMenuOption({ option }) {
87
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
88
+ /* @__PURE__ */ jsx("span", {
89
+ className: "min-w-10 text-gdt-sm font-semibold text-fg-brand",
90
+ children: option.currency
91
+ }),
92
+ /* @__PURE__ */ jsxs("span", {
93
+ className: "ml-auto flex min-w-0 flex-col items-end",
94
+ children: [/* @__PURE__ */ jsx("span", {
95
+ className: "text-gdt-sm font-bold text-fg-primary",
96
+ children: option.value
97
+ }), option.description ? /* @__PURE__ */ jsx("span", {
98
+ className: "text-gdt-xs text-fg-secondary",
99
+ children: option.description
100
+ }) : null]
101
+ }),
102
+ option.selected ? /* @__PURE__ */ jsx(Check, {
103
+ "aria-hidden": "true",
104
+ className: "size-4 shrink-0 text-fg-brand"
105
+ }) : null
106
+ ] });
107
+ }
108
+ /**
109
+ * Rounded-box price display, optionally a trigger for a currency dropdown.
110
+ *
111
+ * Display only: it prints `value` and `description` as given and never
112
+ * composes a sentence, picks a format, or decides that a line is too small to
113
+ * be worth showing. Wording, currency conversion and number formatting all
114
+ * belong to the app, which is the only place that knows the locale, the
115
+ * product's voice, and what the second line is actually for.
116
+ *
117
+ * The legacy `amount` / `shortAmount` / `format` / `monthlyPayment` props still
118
+ * work and keep their old behaviour exactly, including the size threshold that
119
+ * hid the monthly line below `md`. They go in the next major.
120
+ */
121
+ function PriceTag({ value, description, size = "md", currencies, selectedCurrency, onCurrencyChange, renderMenuOption, open, defaultOpen, onOpenChange, className, popoverClassName, type = "button", amount, shortAmount, format = "full", monthlyPayment, monthlyPaymentLabel = "Low as", ...props }) {
31
122
  const hasDropdown = Boolean(currencies?.length);
32
- const displayMonthlyPayment = (size === "md" || size === "lg") && monthlyPayment ? `${monthlyPaymentLabel} ${monthlyPayment} monthly` : void 0;
123
+ const resolvedValue = value ?? getDisplayAmount({
124
+ amount,
125
+ shortAmount,
126
+ format
127
+ });
128
+ const legacyDescription = monthlyPayment && (size === "md" || size === "lg") ? `${monthlyPaymentLabel} ${monthlyPayment} monthly` : void 0;
129
+ const resolvedDescription = description ?? legacyDescription;
33
130
  const pill = /* @__PURE__ */ jsxs("span", {
34
- className: "flex min-w-0 flex-col gap-1",
35
- children: [/* @__PURE__ */ jsx("span", { children: getDisplayAmount({
36
- amount,
37
- shortAmount,
38
- format
39
- }) }), displayMonthlyPayment ? /* @__PURE__ */ jsx("span", {
40
- className: cn("hidden text-gdt-subtext font-medium leading-none opacity-90", {
41
- "inline-block": size === "md",
42
- "inline-block text-gdt-xs": size === "lg"
43
- }),
44
- children: displayMonthlyPayment
131
+ className: cn("flex min-w-0 flex-col text-start", lineGapClassName[size]),
132
+ children: [/* @__PURE__ */ jsxs("span", {
133
+ className: cn("flex items-center justify-between", chevronGapClassName[size]),
134
+ children: [/* @__PURE__ */ jsx("span", {
135
+ className: "min-w-0",
136
+ children: resolvedValue
137
+ }), hasDropdown ? /* @__PURE__ */ jsx(ChevronDown, {
138
+ "aria-hidden": "true",
139
+ className: "shrink-0 transition-transform group-data-popup-open/price-tag:rotate-180"
140
+ }) : null]
141
+ }), resolvedDescription ? /* @__PURE__ */ jsx("span", {
142
+ className: cn("font-medium opacity-90", descriptionSizeClassName[size]),
143
+ children: resolvedDescription
45
144
  }) : null]
46
145
  });
146
+ const pillClassName = cn(priceTagVariants({
147
+ size,
148
+ hasDescription: Boolean(resolvedDescription)
149
+ }), className);
47
150
  if (!hasDropdown) return /* @__PURE__ */ jsx("span", {
48
151
  "data-slot": "price-tag",
49
- className: cn(priceTagVariants({
50
- size,
51
- hasMonthlyPayment: Boolean(displayMonthlyPayment)
52
- }), className),
152
+ className: pillClassName,
153
+ ...props,
53
154
  children: pill
54
155
  });
156
+ const options = (currencies ?? []).map((currency) => resolveMenuOption(currency, format, selectedCurrency));
55
157
  return /* @__PURE__ */ jsxs(Popover.Root, {
56
158
  open,
57
159
  defaultOpen,
58
160
  onOpenChange,
59
- children: [/* @__PURE__ */ jsxs(Popover.Trigger, {
161
+ children: [/* @__PURE__ */ jsx(Popover.Trigger, {
60
162
  "data-slot": "price-tag",
61
163
  "data-interactive": "true",
62
164
  type,
63
- className: cn(priceTagVariants({
64
- size,
65
- hasMonthlyPayment: Boolean(displayMonthlyPayment)
66
- }), className),
165
+ className: pillClassName,
67
166
  ...props,
68
- children: [pill, /* @__PURE__ */ jsx(ChevronDown, {
69
- "aria-hidden": "true",
70
- className: "transition-transform group-data-popup-open/price-tag:rotate-180"
71
- })]
167
+ children: pill
72
168
  }), /* @__PURE__ */ jsx(Popover.Portal, { children: /* @__PURE__ */ jsx(Popover.Positioner, {
73
169
  sideOffset: 10,
74
170
  align: "start",
75
171
  children: /* @__PURE__ */ jsx(Popover.Popup, {
76
172
  "data-slot": "price-tag-popover",
77
173
  className: cn("z-50 flex w-[17.25rem] max-w-[calc(100vw-2rem)] origin-(--transform-origin) flex-col rounded-xl border border-line-subtle bg-surface-overlay p-2 text-fg-primary shadow-3 outline-hidden transition-[opacity,scale,translate] duration-[var(--duration-motion-popup)] ease-gdt-out data-starting-style:opacity-0 data-starting-style:scale-95 data-ending-style:opacity-0 data-ending-style:scale-95 data-[side=bottom]:data-starting-style:translate-y-2 data-[side=bottom]:data-ending-style:translate-y-2 data-[side=top]:data-starting-style:-translate-y-2 data-[side=top]:data-ending-style:-translate-y-2 motion-reduce:data-starting-style:scale-100 motion-reduce:data-ending-style:scale-100 motion-reduce:data-[side=bottom]:data-starting-style:translate-y-0 motion-reduce:data-[side=bottom]:data-ending-style:translate-y-0 motion-reduce:data-[side=top]:data-starting-style:translate-y-0 motion-reduce:data-[side=top]:data-ending-style:translate-y-0", popoverClassName),
78
- children: currencies?.map((currency) => {
79
- const active = currency.code === selectedCurrency;
80
- const optionAmount = getDisplayAmount({
81
- amount: currency.amount,
82
- shortAmount: currency.shortAmount,
83
- format
84
- });
85
- const optionMonthlyPayment = currency.monthlyPayment ? `${currency.monthlyPaymentLabel ?? "As low as"} ${currency.monthlyPayment} per month` : void 0;
86
- return /* @__PURE__ */ jsxs("button", {
87
- type: "button",
88
- "data-active": active ? "true" : void 0,
89
- className: "flex min-h-14 w-full items-center gap-3 rounded-lg px-3 text-start outline-none transition-colors hover:bg-surface-brand-subtle focus-visible:bg-surface-brand-subtle focus-visible:shadow-[0px_0px_0px_2px_var(--color-line-focus)] data-[active=true]:bg-surface-brand-subtle",
90
- onClick: () => onCurrencyChange?.(currency),
91
- children: [
92
- /* @__PURE__ */ jsx("span", {
93
- className: "min-w-10 text-gdt-sm font-semibold text-fg-brand",
94
- children: currency.code
95
- }),
96
- /* @__PURE__ */ jsxs("span", {
97
- className: "ml-auto flex min-w-0 flex-col items-end",
98
- children: [/* @__PURE__ */ jsx("span", {
99
- className: "text-gdt-sm font-bold text-fg-primary",
100
- children: optionAmount
101
- }), optionMonthlyPayment ? /* @__PURE__ */ jsx("span", {
102
- className: "text-gdt-xs text-fg-secondary",
103
- children: optionMonthlyPayment
104
- }) : null]
105
- }),
106
- active ? /* @__PURE__ */ jsx(Check, {
107
- "aria-hidden": "true",
108
- className: "size-4 shrink-0 text-fg-brand"
109
- }) : null
110
- ]
111
- }, currency.code);
112
- })
174
+ children: options.map((option) => /* @__PURE__ */ jsx("button", {
175
+ type: "button",
176
+ "data-slot": "price-tag-option",
177
+ "data-active": option.selected ? "true" : void 0,
178
+ className: "flex min-h-14 w-full items-center gap-3 rounded-lg px-3 text-start outline-none transition-colors hover:bg-surface-brand-subtle focus-visible:bg-surface-brand-subtle focus-visible:shadow-[0px_0px_0px_2px_var(--color-line-focus)] data-[active=true]:bg-surface-brand-subtle",
179
+ onClick: () => onCurrencyChange?.(option),
180
+ children: renderMenuOption ? renderMenuOption(option) : /* @__PURE__ */ jsx(DefaultMenuOption, { option })
181
+ }, option.currency))
113
182
  })
114
183
  }) })]
115
184
  });
package/dist/progress.js CHANGED
@@ -1,5 +1,5 @@
1
1
  "use client";
2
- import { t as cn } from "./cn-BI_4DMBf.js";
2
+ import { cn } from "./utils/cn.js";
3
3
  import { SizeProvider, useComponentSize } from "./size-context.js";
4
4
  import { jsx, jsxs } from "react/jsx-runtime";
5
5
  import { cva } from "class-variance-authority";