@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
package/dist/styles.css CHANGED
@@ -164,6 +164,7 @@
164
164
  --animate-accordion-up: accordion-up 0.2s ease-out;
165
165
  --animate-sonar-glow: sonar-glow 1.5s cubic-bezier(0, 0, 0.2, 1) infinite;
166
166
  --animate-round-sonar-glow: round-sonar-glow 1.5s ease-out infinite;
167
+ --animate-sonar-wave: sonar-wave 1.5s cubic-bezier(0, 0, 0.2, 1) infinite;
167
168
  --animate-spin-slow: spin 3s linear infinite;
168
169
  }
169
170
  }
@@ -479,6 +480,15 @@
479
480
  .bottom-16 {
480
481
  bottom: calc(var(--spacing) * 16);
481
482
  }
483
+ .bottom-20 {
484
+ bottom: calc(var(--spacing) * 20);
485
+ }
486
+ .bottom-24 {
487
+ bottom: calc(var(--spacing) * 24);
488
+ }
489
+ .bottom-28 {
490
+ bottom: calc(var(--spacing) * 28);
491
+ }
482
492
  .bottom-\[18px\] {
483
493
  bottom: 18px;
484
494
  }
@@ -721,6 +731,9 @@
721
731
  .block {
722
732
  display: block;
723
733
  }
734
+ .contents {
735
+ display: contents;
736
+ }
724
737
  .flex {
725
738
  display: flex;
726
739
  }
@@ -1555,6 +1568,9 @@
1555
1568
  .animate-pulse {
1556
1569
  animation: var(--animate-pulse);
1557
1570
  }
1571
+ .animate-sonar-wave {
1572
+ animation: var(--animate-sonar-wave);
1573
+ }
1558
1574
  .animate-spin {
1559
1575
  animation: var(--animate-spin);
1560
1576
  }
@@ -3692,6 +3708,15 @@
3692
3708
  .\[--slider-track-size\:0\.375rem\] {
3693
3709
  --slider-track-size: 0.375rem;
3694
3710
  }
3711
+ .\[--sonar-spread\:0\.375rem\] {
3712
+ --sonar-spread: 0.375rem;
3713
+ }
3714
+ .\[--sonar-spread\:0\.625rem\] {
3715
+ --sonar-spread: 0.625rem;
3716
+ }
3717
+ .\[--sonar-spread\:0\.875rem\] {
3718
+ --sonar-spread: 0.875rem;
3719
+ }
3695
3720
  .\[--switch-spinner-size\:0\.75rem\] {
3696
3721
  --switch-spinner-size: 0.75rem;
3697
3722
  }
@@ -6447,6 +6472,16 @@
6447
6472
  }
6448
6473
  }
6449
6474
  }
6475
+ .hover\:bg-black\/60 {
6476
+ &:hover {
6477
+ @media (hover: hover) {
6478
+ background-color: color-mix(in srgb, #000 60%, transparent);
6479
+ @supports (color: color-mix(in lab, red, red)) {
6480
+ background-color: color-mix(in oklab, var(--color-black) 60%, transparent);
6481
+ }
6482
+ }
6483
+ }
6484
+ }
6450
6485
  .hover\:bg-black\/80 {
6451
6486
  &:hover {
6452
6487
  @media (hover: hover) {
@@ -7143,9 +7178,19 @@
7143
7178
  border-radius: var(--radius-xl);
7144
7179
  }
7145
7180
  }
7146
- .has-data-\[slot\=combobox-chip\]\:px-1 {
7181
+ .has-data-\[slot\=combobox-chip\]\:px-1\.5 {
7147
7182
  &:has(*[data-slot="combobox-chip"]) {
7148
- padding-inline: var(--spacing);
7183
+ padding-inline: calc(var(--spacing) * 1.5);
7184
+ }
7185
+ }
7186
+ .has-data-\[slot\=combobox-chip\]\:px-2 {
7187
+ &:has(*[data-slot="combobox-chip"]) {
7188
+ padding-inline: calc(var(--spacing) * 2);
7189
+ }
7190
+ }
7191
+ .has-data-\[slot\=combobox-chip\]\:px-2\.5 {
7192
+ &:has(*[data-slot="combobox-chip"]) {
7193
+ padding-inline: calc(var(--spacing) * 2.5);
7149
7194
  }
7150
7195
  }
7151
7196
  .has-data-\[slot\=combobox-chip-remove\]\:pr-0 {
@@ -8326,6 +8371,11 @@
8326
8371
  box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
8327
8372
  }
8328
8373
  }
8374
+ .data-\[grown\=true\]\:rounded-2xl {
8375
+ &[data-grown="true"] {
8376
+ border-radius: var(--radius-2xl);
8377
+ }
8378
+ }
8329
8379
  .data-\[interactive\=true\]\:cursor-pointer {
8330
8380
  &[data-interactive="true"] {
8331
8381
  cursor: pointer;
@@ -9647,6 +9697,11 @@
9647
9697
  backdrop-filter: var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);
9648
9698
  }
9649
9699
  }
9700
+ .motion-reduce\:hidden {
9701
+ @media (prefers-reduced-motion: reduce) {
9702
+ display: none;
9703
+ }
9704
+ }
9650
9705
  .motion-reduce\:scale-100 {
9651
9706
  @media (prefers-reduced-motion: reduce) {
9652
9707
  --tw-scale-x: 100%;
@@ -9660,6 +9715,17 @@
9660
9715
  animation: none;
9661
9716
  }
9662
9717
  }
9718
+ .motion-reduce\:opacity-40 {
9719
+ @media (prefers-reduced-motion: reduce) {
9720
+ opacity: 40%;
9721
+ }
9722
+ }
9723
+ .motion-reduce\:shadow-\[0_0_0_2px_currentColor\] {
9724
+ @media (prefers-reduced-motion: reduce) {
9725
+ --tw-shadow: 0 0 0 2px var(--tw-shadow-color, currentColor);
9726
+ box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
9727
+ }
9728
+ }
9663
9729
  .motion-reduce\:transition-colors {
9664
9730
  @media (prefers-reduced-motion: reduce) {
9665
9731
  transition-property: color, background-color, border-color, outline-color, text-decoration-color, fill, stroke, --tw-gradient-from, --tw-gradient-via, --tw-gradient-to;
@@ -11208,6 +11274,12 @@
11208
11274
  pointer-events: none;
11209
11275
  }
11210
11276
  }
11277
+ .\[\&_svg\]\:size-3 {
11278
+ & svg {
11279
+ width: calc(var(--spacing) * 3);
11280
+ height: calc(var(--spacing) * 3);
11281
+ }
11282
+ }
11211
11283
  .\[\&_svg\]\:size-3\.5 {
11212
11284
  & svg {
11213
11285
  width: calc(var(--spacing) * 3.5);
@@ -12451,6 +12523,7 @@
12451
12523
  --animate-bouncing-loader: none;
12452
12524
  --animate-sonar-glow: none;
12453
12525
  --animate-round-sonar-glow: none;
12526
+ --animate-sonar-wave: none;
12454
12527
  --animate-spin-slow: none;
12455
12528
  }
12456
12529
  }
@@ -12533,6 +12606,16 @@
12533
12606
  opacity: 0;
12534
12607
  }
12535
12608
  }
12609
+ @keyframes sonar-wave {
12610
+ from {
12611
+ box-shadow: 0 0 0 0 currentColor;
12612
+ opacity: 0.55;
12613
+ }
12614
+ to {
12615
+ box-shadow: 0 0 0 var(--sonar-spread, 0.625rem) currentColor;
12616
+ opacity: 0;
12617
+ }
12618
+ }
12536
12619
  @layer base {
12537
12620
  body {
12538
12621
  display: flex;
package/dist/switch.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 { useComponentSize } from "./size-context.js";
4
4
  import { jsx } from "react/jsx-runtime";
5
5
  import { cva } from "class-variance-authority";
@@ -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 { Fragment, jsx, jsxs } from "react/jsx-runtime";
4
4
  import { mergeProps } from "@base-ui/react/merge-props";
5
5
  import { useRender } from "@base-ui/react/use-render";
package/dist/table.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 { jsx } from "react/jsx-runtime";
4
4
  //#region src/table.tsx
5
5
  function getPinnedDividerClassName(pinnedSide) {
package/dist/tabs.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";
package/dist/tag.js CHANGED
@@ -1,6 +1,6 @@
1
1
  "use client";
2
2
  import { X } 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 { Fragment, jsx, jsxs } from "react/jsx-runtime";
6
6
  import { cva } from "class-variance-authority";
@@ -1,6 +1,6 @@
1
1
  "use client";
2
2
  import { ArrowUpRight, Play, Star } from "./icons.js";
3
- import { t as cn } from "./cn-BI_4DMBf.js";
3
+ import { cn } from "./utils/cn.js";
4
4
  import { Avatar, AvatarFallback, AvatarImage } from "./avatar.js";
5
5
  import { buttonVariants } from "./button.js";
6
6
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
package/dist/textarea.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 { useComponentSize } from "./size-context.js";
4
4
  import { jsx } from "react/jsx-runtime";
5
5
  import { cva } from "class-variance-authority";
@@ -1,6 +1,6 @@
1
1
  "use client";
2
2
  import { ChevronDown, Monitor, Moon, Sun } 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 { DropdownMenu, DropdownMenuContent, DropdownMenuRadioGroup, DropdownMenuRadioItem, DropdownMenuTrigger } from "./dropdown-menu.js";
6
6
  import { jsx, jsxs } from "react/jsx-runtime";
@@ -1,7 +1,8 @@
1
1
  import { n as InputSize } from "./input-CfoEJ8A6.js";
2
+ import { t as PickerPlacementProps } from "./picker-CsnHtbe4.js";
2
3
  import * as React from "react";
3
4
  //#region src/time-picker.d.ts
4
- interface TimePickerProps {
5
+ interface TimePickerProps extends PickerPlacementProps {
5
6
  value?: string;
6
7
  onChange?: (time: string | undefined) => void;
7
8
  format?: "12h" | "24h";
@@ -11,6 +12,6 @@ interface TimePickerProps {
11
12
  placeholder?: string;
12
13
  className?: string;
13
14
  }
14
- declare function TimePicker({ value, onChange, format, disabled, size, placeholder, className }: TimePickerProps): React.JSX.Element;
15
+ declare function TimePicker({ value, onChange, format, disabled, size, placeholder, className, ...placement }: TimePickerProps): React.JSX.Element;
15
16
  //#endregion
16
17
  export { TimePicker, type TimePickerProps };
@@ -1,9 +1,9 @@
1
1
  "use client";
2
2
  import { Clock } from "./icons.js";
3
- import { t as cn } from "./cn-BI_4DMBf.js";
4
- import { t as useReducedMotion } from "./use-reduced-motion-BDvOK14x.js";
3
+ import { cn } from "./utils/cn.js";
4
+ import { useReducedMotion } from "./utils/use-reduced-motion.js";
5
5
  import { Popover, PopoverContent } from "./popover.js";
6
- import { n as PickerFooter, r as PickerTrigger, t as PICKER_COLLISION_AVOIDANCE } from "./picker-xSTzeYhc.js";
6
+ import { n as PickerTrigger, t as PickerFooter } from "./picker-DmIiQKLJ.js";
7
7
  import { jsx, jsxs } from "react/jsx-runtime";
8
8
  import * as React from "react";
9
9
  //#region src/lib/time-picker-paint.ts
@@ -239,7 +239,7 @@ function TimeColumn({ label, items, value, onChange }) {
239
239
  })]
240
240
  });
241
241
  }
242
- function TimePicker({ value, onChange, format = "12h", disabled = false, size, placeholder = "Select time", className }) {
242
+ function TimePicker({ value, onChange, format = "12h", disabled = false, size, placeholder = "Select time", className, ...placement }) {
243
243
  const [open, setOpen] = React.useState(false);
244
244
  const [pending, setPending] = React.useState(() => parseTimeString(value, format));
245
245
  function handleOpenChange(next) {
@@ -273,10 +273,8 @@ function TimePicker({ value, onChange, format = "12h", disabled = false, size, p
273
273
  className
274
274
  }), /* @__PURE__ */ jsxs(PopoverContent, {
275
275
  showArrow: false,
276
- align: "center",
277
- side: "bottom",
278
- collisionAvoidance: PICKER_COLLISION_AVOIDANCE,
279
276
  className: "min-w-(--anchor-width) gap-0 overflow-hidden rounded-2xl p-0 border border-line-subtle",
277
+ ...placement,
280
278
  children: [
281
279
  /* @__PURE__ */ jsx("div", {
282
280
  className: "flex w-full items-center justify-center gap-2 border-b border-line-subtle",
package/dist/toast.js CHANGED
@@ -1,6 +1,6 @@
1
1
  "use client";
2
2
  import { Check, Info, 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 { jsx, jsxs } from "react/jsx-runtime";
6
6
  import { Toast } from "@base-ui/react/toast";
package/dist/tooltip.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 { jsx, jsxs } from "react/jsx-runtime";
4
4
  import { Tooltip as Tooltip$1 } from "@base-ui/react/tooltip";
5
5
  //#region src/tooltip.tsx
@@ -1,6 +1,6 @@
1
1
  "use client";
2
2
  import { ArrowUpRight, ChevronDown } from "./icons.js";
3
- import { t as cn } from "./cn-BI_4DMBf.js";
3
+ import { cn } from "./utils/cn.js";
4
4
  import { MobileSidebar } from "./mobile-sidebar.js";
5
5
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
6
6
  import { mergeProps } from "@base-ui/react/merge-props";
package/dist/tree-map.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 { useComponentSize } from "./size-context.js";
4
4
  import { ChartCard, ChartContainer, ChartDescription, ChartHeader, ChartTitle, ChartTooltip, ChartTooltipContent } from "./chart.js";
5
5
  import { jsx, jsxs } from "react/jsx-runtime";
@@ -0,0 +1,5 @@
1
+ import { ClassValue } from "clsx";
2
+ //#region src/utils/cn.d.ts
3
+ declare function cn(...inputs: ClassValue[]): string;
4
+ //#endregion
5
+ export { cn };
@@ -0,0 +1,33 @@
1
+ import { clsx } from "clsx";
2
+ import { extendTailwindMerge } from "tailwind-merge";
3
+ //#region src/utils/cn.ts
4
+ /**
5
+ * The class merger every component in this package uses.
6
+ *
7
+ * Published because overriding a component's classes safely means merging them
8
+ * the same way it does. `clsx` alone leaves both `text-gdt-sm` and your
9
+ * `text-gdt-lg` on the element and lets source order decide; a stock
10
+ * `tailwind-merge` does not know the `text-gdt-*` scale is a font size and
11
+ * keeps both for a different reason. This one is configured for the design
12
+ * tokens, so the last class wins the way you meant it to.
13
+ */
14
+ const twMerge = extendTailwindMerge({ extend: { classGroups: { "font-size": [{ text: [
15
+ "gdt-display",
16
+ "gdt-h1",
17
+ "gdt-h2",
18
+ "gdt-h3",
19
+ "gdt-h4",
20
+ "gdt-h5",
21
+ "gdt-xl",
22
+ "gdt-lg",
23
+ "gdt-md",
24
+ "gdt-sm",
25
+ "gdt-xs",
26
+ "gdt-subtext",
27
+ "gdt-capitalized"
28
+ ] }] } } });
29
+ function cn(...inputs) {
30
+ return twMerge(clsx(inputs));
31
+ }
32
+ //#endregion
33
+ export { cn };
@@ -0,0 +1,45 @@
1
+ //#region src/utils/format.d.ts
2
+ /**
3
+ * Formatters the components here already use, published because the same
4
+ * values get written outside a component as often as inside one — a duration
5
+ * in a table cell, initials on a server-rendered avatar, a date in an export.
6
+ *
7
+ * Every one is a pure function with no React and no styling, so a server-only
8
+ * module can import them.
9
+ */
10
+ /**
11
+ * Seconds as a clock: `3723` → `"1:02:03"`, `95` → `"1:35"`. The hours part is
12
+ * dropped below an hour rather than padded to `0:01:35`, so a short clip does
13
+ * not read as if it might be long.
14
+ *
15
+ * Anything that is not a finite number is `"0:00"` — a media element reports
16
+ * `NaN` for its duration until it has loaded metadata, and the alternative is
17
+ * `NaN:NaN` on screen for the first few frames.
18
+ */
19
+ declare function formatDuration(value: number | undefined): string;
20
+ /**
21
+ * A date written out in full: `"08 August 2026"`. Day before month, because
22
+ * the product's audience reads it that way and `"8/8/2026"` is ambiguous
23
+ * between two conventions where a spelled month is not.
24
+ */
25
+ declare function formatDate(value: Date, locale?: string): string;
26
+ /**
27
+ * A time of day, 24-hour: `"14:05"`. A string passes through untouched, so a
28
+ * caller that already has the wording it wants — `"Yesterday"`, `"just now"` —
29
+ * can hand it over without this second-guessing it.
30
+ */
31
+ declare function formatTimestamp(value: Date | string, locale?: string): string;
32
+ /**
33
+ * A `datetime` attribute's value for a `<time>` element. A `Date` becomes ISO;
34
+ * a string is assumed to already be in a machine-readable form and passes
35
+ * through, matching {@link formatTimestamp}.
36
+ */
37
+ declare function toDateTimeAttribute(value: Date | string): string;
38
+ /**
39
+ * The first letter of the first two words: `"Daniel Ikoyo"` → `"DI"`. Two and
40
+ * not three, because a monogram is a fallback for a missing photo and a third
41
+ * letter starts to compete with the name beside it.
42
+ */
43
+ declare function getInitials(name: string): string;
44
+ //#endregion
45
+ export { formatDate, formatDuration, formatTimestamp, getInitials, toDateTimeAttribute };
@@ -0,0 +1,70 @@
1
+ //#region src/utils/format.ts
2
+ /**
3
+ * Formatters the components here already use, published because the same
4
+ * values get written outside a component as often as inside one — a duration
5
+ * in a table cell, initials on a server-rendered avatar, a date in an export.
6
+ *
7
+ * Every one is a pure function with no React and no styling, so a server-only
8
+ * module can import them.
9
+ */
10
+ /**
11
+ * Seconds as a clock: `3723` → `"1:02:03"`, `95` → `"1:35"`. The hours part is
12
+ * dropped below an hour rather than padded to `0:01:35`, so a short clip does
13
+ * not read as if it might be long.
14
+ *
15
+ * Anything that is not a finite number is `"0:00"` — a media element reports
16
+ * `NaN` for its duration until it has loaded metadata, and the alternative is
17
+ * `NaN:NaN` on screen for the first few frames.
18
+ */
19
+ function formatDuration(value) {
20
+ if (!Number.isFinite(value)) return "0:00";
21
+ const safeValue = Math.max(0, Math.floor(value ?? 0));
22
+ const hours = Math.floor(safeValue / 3600);
23
+ const minutes = Math.floor(safeValue % 3600 / 60);
24
+ const seconds = safeValue % 60;
25
+ if (hours > 0) return `${hours}:${String(minutes).padStart(2, "0")}:${String(seconds).padStart(2, "0")}`;
26
+ return `${minutes}:${String(seconds).padStart(2, "0")}`;
27
+ }
28
+ /**
29
+ * A date written out in full: `"08 August 2026"`. Day before month, because
30
+ * the product's audience reads it that way and `"8/8/2026"` is ambiguous
31
+ * between two conventions where a spelled month is not.
32
+ */
33
+ function formatDate(value, locale = "en-GB") {
34
+ return value.toLocaleDateString(locale, {
35
+ day: "2-digit",
36
+ month: "long",
37
+ year: "numeric"
38
+ });
39
+ }
40
+ /**
41
+ * A time of day, 24-hour: `"14:05"`. A string passes through untouched, so a
42
+ * caller that already has the wording it wants — `"Yesterday"`, `"just now"` —
43
+ * can hand it over without this second-guessing it.
44
+ */
45
+ function formatTimestamp(value, locale) {
46
+ if (typeof value === "string") return value;
47
+ return value.toLocaleTimeString(locale ? [locale] : [], {
48
+ hour: "2-digit",
49
+ minute: "2-digit",
50
+ hour12: false
51
+ });
52
+ }
53
+ /**
54
+ * A `datetime` attribute's value for a `<time>` element. A `Date` becomes ISO;
55
+ * a string is assumed to already be in a machine-readable form and passes
56
+ * through, matching {@link formatTimestamp}.
57
+ */
58
+ function toDateTimeAttribute(value) {
59
+ return value instanceof Date ? value.toISOString() : value;
60
+ }
61
+ /**
62
+ * The first letter of the first two words: `"Daniel Ikoyo"` → `"DI"`. Two and
63
+ * not three, because a monogram is a fallback for a missing photo and a third
64
+ * letter starts to compete with the name beside it.
65
+ */
66
+ function getInitials(name) {
67
+ return name.trim().split(/\s+/).slice(0, 2).map((part) => part[0]?.toUpperCase()).join("");
68
+ }
69
+ //#endregion
70
+ export { formatDate, formatDuration, formatTimestamp, getInitials, toDateTimeAttribute };
@@ -0,0 +1,98 @@
1
+ import { CountryCode } from "libphonenumber-js";
2
+ //#region src/utils/phone.d.ts
3
+ /**
4
+ * Phone number helpers, shared by `PhoneInput` and by the forms around it.
5
+ *
6
+ * These are here because a phone number is validated in more places than it is
7
+ * typed — a zod schema, a server action, a CSV import, a table cell — and every
8
+ * one of those was otherwise reaching for its own regex. They carry no React
9
+ * and no styling, so a server-only module can import them.
10
+ *
11
+ * One rule runs through all of them: **invalid in, empty out**. Anything that
12
+ * is not a real, dialable number formats and parses to `""` rather than to
13
+ * `null`, to `undefined`, or to a half-cleaned version of what you passed. A
14
+ * caller then has one thing to check instead of three.
15
+ */
16
+ /** How a number should read once it is formatted. */
17
+ type PhoneNumberFormat =
18
+ /** `+234 801 234 5678` — the one to show when the country matters. */
19
+ "international" |
20
+ /** `0801 234 5678` — the one to show to someone in that country. */
21
+ "national" |
22
+ /** `+2348012345678` — the one to store. */
23
+ "e164" |
24
+ /** `tel:+234-801-234-5678` — the one to put in an `href`. */
25
+ "rfc3966";
26
+ interface PhoneNumberOptions {
27
+ /**
28
+ * The country to read a number against when it does not carry its own
29
+ * calling code. `"08012345678"` is a valid Nigerian number and nothing at
30
+ * all without this; `"+2348012345678"` needs it either way.
31
+ */
32
+ country?: CountryCode;
33
+ }
34
+ /**
35
+ * Whether a value is a phone number that could actually be dialed. Stricter
36
+ * than "has the right number of digits" — it checks the number against the
37
+ * ranges its country has assigned.
38
+ *
39
+ * Written to be passed by reference to a schema:
40
+ *
41
+ * ```ts
42
+ * z.string().refine(validatePhoneNumber, "Enter a valid phone number");
43
+ * yup.string().test("phone", "Enter a valid phone number", validatePhoneNumber);
44
+ * ```
45
+ *
46
+ * Empty and `null` are invalid. Mark the field optional in the schema rather
47
+ * than asking this to have an opinion about whether a missing number is a
48
+ * problem, which is the schema's business and not this function's.
49
+ */
50
+ declare function validatePhoneNumber(value: unknown, options?: PhoneNumberOptions): boolean;
51
+ /**
52
+ * The E.164 form of a valid number — `+2348012345678`, the one to store — or
53
+ * `""` if the value is not a valid number.
54
+ *
55
+ * ```ts
56
+ * parsePhoneNumber("0801 234 5678", { country: "NG" }); // "+2348012345678"
57
+ * parsePhoneNumber("0801 234"); // ""
58
+ * ```
59
+ */
60
+ declare function parsePhoneNumber(value: unknown, options?: PhoneNumberOptions): string;
61
+ /**
62
+ * A valid number written for a reader, or `""` if the value is not a valid
63
+ * number. Defaults to `international`, which is the form that does not depend
64
+ * on knowing where the reader is.
65
+ *
66
+ * ```ts
67
+ * formatPhoneNumber("+2348012345678"); // "+234 801 234 5678"
68
+ * formatPhoneNumber("+2348012345678", { format: "national" }); // "0801 234 5678"
69
+ * ```
70
+ *
71
+ * Blanking an invalid number is the point rather than an oversight: a value
72
+ * that cannot be formatted is a value that should be fixed, and showing a
73
+ * half-formatted one hides that.
74
+ */
75
+ declare function formatPhoneNumber(value: unknown, options?: PhoneNumberOptions & {
76
+ format?: PhoneNumberFormat;
77
+ }): string;
78
+ /** The country a valid number belongs to, or `undefined`. */
79
+ declare function getPhoneNumberCountry(value: unknown, options?: PhoneNumberOptions): CountryCode | undefined;
80
+ interface PhoneCountry {
81
+ /** ISO 3166-1 alpha-2, e.g. `"NG"`. */
82
+ code: CountryCode;
83
+ /** Localised display name, falling back to the code where `Intl` is absent. */
84
+ name: string;
85
+ /** Calling code without the `+`, e.g. `"234"`. */
86
+ callingCode: string;
87
+ }
88
+ /**
89
+ * Every country a number can be entered for, sorted by name in `locale`.
90
+ *
91
+ * `PhoneInput` builds its own list from this. It is exported because the
92
+ * country a phone number belongs to tends to be asked for elsewhere in the
93
+ * same form — an address, a nationality — and a second, differently-sorted
94
+ * list of countries in one form is a bug people notice.
95
+ */
96
+ declare function getPhoneCountries(locale?: string): PhoneCountry[];
97
+ //#endregion
98
+ export { type CountryCode, type PhoneCountry, type PhoneNumberFormat, type PhoneNumberOptions, formatPhoneNumber, getPhoneCountries, getPhoneNumberCountry, parsePhoneNumber, validatePhoneNumber };