@giddaa-housing/ui 0.1.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 +41 -0
- package/dist/accordion.d.ts +11 -0
- package/dist/accordion.js +124 -0
- package/dist/accordion.js.map +1 -0
- package/dist/alert.d.ts +18 -0
- package/dist/alert.js +152 -0
- package/dist/alert.js.map +1 -0
- package/dist/avatar.d.ts +14 -0
- package/dist/avatar.js +152 -0
- package/dist/avatar.js.map +1 -0
- package/dist/badge.d.ts +14 -0
- package/dist/badge.js +119 -0
- package/dist/badge.js.map +1 -0
- package/dist/bar-chart.d.ts +136 -0
- package/dist/bar-chart.js +302 -0
- package/dist/bar-chart.js.map +1 -0
- package/dist/base.css +32 -0
- package/dist/breadcrumb.d.ts +12 -0
- package/dist/breadcrumb.js +161 -0
- package/dist/breadcrumb.js.map +1 -0
- package/dist/bullet-chart.d.ts +98 -0
- package/dist/bullet-chart.js +189 -0
- package/dist/bullet-chart.js.map +1 -0
- package/dist/button-group.d.ts +18 -0
- package/dist/button-group.js +170 -0
- package/dist/button-group.js.map +1 -0
- package/dist/button.d.ts +19 -0
- package/dist/button.js +146 -0
- package/dist/button.js.map +1 -0
- package/dist/calendar.d.ts +9 -0
- package/dist/calendar.js +293 -0
- package/dist/calendar.js.map +1 -0
- package/dist/card.d.ts +18 -0
- package/dist/card.js +186 -0
- package/dist/card.js.map +1 -0
- package/dist/carousel.d.ts +53 -0
- package/dist/carousel.js +396 -0
- package/dist/carousel.js.map +1 -0
- package/dist/chart.d.ts +122 -0
- package/dist/chart.js +494 -0
- package/dist/chart.js.map +1 -0
- package/dist/checkbox.d.ts +73 -0
- package/dist/checkbox.js +87 -0
- package/dist/checkbox.js.map +1 -0
- package/dist/collapsible.d.ts +8 -0
- package/dist/collapsible.js +82 -0
- package/dist/collapsible.js.map +1 -0
- package/dist/combobox.d.ts +30 -0
- package/dist/combobox.js +713 -0
- package/dist/combobox.js.map +1 -0
- package/dist/date-picker.d.ts +22 -0
- package/dist/date-picker.js +528 -0
- package/dist/date-picker.js.map +1 -0
- package/dist/dialog.d.ts +30 -0
- package/dist/dialog.js +339 -0
- package/dist/dialog.js.map +1 -0
- package/dist/dropdown-menu.d.ts +35 -0
- package/dist/dropdown-menu.js +376 -0
- package/dist/dropdown-menu.js.map +1 -0
- package/dist/dropzone.d.ts +128 -0
- package/dist/dropzone.js +490 -0
- package/dist/dropzone.js.map +1 -0
- package/dist/empty-state.d.ts +17 -0
- package/dist/empty-state.js +146 -0
- package/dist/empty-state.js.map +1 -0
- package/dist/field.d.ts +29 -0
- package/dist/field.js +345 -0
- package/dist/field.js.map +1 -0
- package/dist/files/red-hat-display-latin-300-italic.woff +0 -0
- package/dist/files/red-hat-display-latin-300-italic.woff2 +0 -0
- package/dist/files/red-hat-display-latin-300-normal.woff +0 -0
- package/dist/files/red-hat-display-latin-300-normal.woff2 +0 -0
- package/dist/files/red-hat-display-latin-400-italic.woff +0 -0
- package/dist/files/red-hat-display-latin-400-italic.woff2 +0 -0
- package/dist/files/red-hat-display-latin-400-normal.woff +0 -0
- package/dist/files/red-hat-display-latin-400-normal.woff2 +0 -0
- package/dist/files/red-hat-display-latin-500-italic.woff +0 -0
- package/dist/files/red-hat-display-latin-500-italic.woff2 +0 -0
- package/dist/files/red-hat-display-latin-500-normal.woff +0 -0
- package/dist/files/red-hat-display-latin-500-normal.woff2 +0 -0
- package/dist/files/red-hat-display-latin-600-italic.woff +0 -0
- package/dist/files/red-hat-display-latin-600-italic.woff2 +0 -0
- package/dist/files/red-hat-display-latin-600-normal.woff +0 -0
- package/dist/files/red-hat-display-latin-600-normal.woff2 +0 -0
- package/dist/files/red-hat-display-latin-700-italic.woff +0 -0
- package/dist/files/red-hat-display-latin-700-italic.woff2 +0 -0
- package/dist/files/red-hat-display-latin-700-normal.woff +0 -0
- package/dist/files/red-hat-display-latin-700-normal.woff2 +0 -0
- package/dist/files/red-hat-display-latin-800-italic.woff +0 -0
- package/dist/files/red-hat-display-latin-800-italic.woff2 +0 -0
- package/dist/files/red-hat-display-latin-800-normal.woff +0 -0
- package/dist/files/red-hat-display-latin-800-normal.woff2 +0 -0
- package/dist/files/red-hat-display-latin-900-italic.woff +0 -0
- package/dist/files/red-hat-display-latin-900-italic.woff2 +0 -0
- package/dist/files/red-hat-display-latin-900-normal.woff +0 -0
- package/dist/files/red-hat-display-latin-900-normal.woff2 +0 -0
- package/dist/files/red-hat-display-latin-ext-300-italic.woff +0 -0
- package/dist/files/red-hat-display-latin-ext-300-italic.woff2 +0 -0
- package/dist/files/red-hat-display-latin-ext-300-normal.woff +0 -0
- package/dist/files/red-hat-display-latin-ext-300-normal.woff2 +0 -0
- package/dist/files/red-hat-display-latin-ext-400-italic.woff +0 -0
- package/dist/files/red-hat-display-latin-ext-400-italic.woff2 +0 -0
- package/dist/files/red-hat-display-latin-ext-400-normal.woff +0 -0
- package/dist/files/red-hat-display-latin-ext-400-normal.woff2 +0 -0
- package/dist/files/red-hat-display-latin-ext-500-italic.woff +0 -0
- package/dist/files/red-hat-display-latin-ext-500-italic.woff2 +0 -0
- package/dist/files/red-hat-display-latin-ext-500-normal.woff +0 -0
- package/dist/files/red-hat-display-latin-ext-500-normal.woff2 +0 -0
- package/dist/files/red-hat-display-latin-ext-600-italic.woff +0 -0
- package/dist/files/red-hat-display-latin-ext-600-italic.woff2 +0 -0
- package/dist/files/red-hat-display-latin-ext-600-normal.woff +0 -0
- package/dist/files/red-hat-display-latin-ext-600-normal.woff2 +0 -0
- package/dist/files/red-hat-display-latin-ext-700-italic.woff +0 -0
- package/dist/files/red-hat-display-latin-ext-700-italic.woff2 +0 -0
- package/dist/files/red-hat-display-latin-ext-700-normal.woff +0 -0
- package/dist/files/red-hat-display-latin-ext-700-normal.woff2 +0 -0
- package/dist/files/red-hat-display-latin-ext-800-italic.woff +0 -0
- package/dist/files/red-hat-display-latin-ext-800-italic.woff2 +0 -0
- package/dist/files/red-hat-display-latin-ext-800-normal.woff +0 -0
- package/dist/files/red-hat-display-latin-ext-800-normal.woff2 +0 -0
- package/dist/files/red-hat-display-latin-ext-900-italic.woff +0 -0
- package/dist/files/red-hat-display-latin-ext-900-italic.woff2 +0 -0
- package/dist/files/red-hat-display-latin-ext-900-normal.woff +0 -0
- package/dist/files/red-hat-display-latin-ext-900-normal.woff2 +0 -0
- package/dist/funnel-chart.d.ts +84 -0
- package/dist/funnel-chart.js +569 -0
- package/dist/funnel-chart.js.map +1 -0
- package/dist/index.d.ts +87 -0
- package/dist/index.js +10448 -0
- package/dist/index.js.map +1 -0
- package/dist/input-group.d.ts +31 -0
- package/dist/input-group.js +380 -0
- package/dist/input-group.js.map +1 -0
- package/dist/input-size-context.d.ts +11 -0
- package/dist/input-size-context.js +24 -0
- package/dist/input-size-context.js.map +1 -0
- package/dist/input.d.ts +13 -0
- package/dist/input.js +83 -0
- package/dist/input.js.map +1 -0
- package/dist/item.d.ts +8 -0
- package/dist/item.js +93 -0
- package/dist/item.js.map +1 -0
- package/dist/kpi-card.d.ts +24 -0
- package/dist/kpi-card.js +243 -0
- package/dist/kpi-card.js.map +1 -0
- package/dist/label.d.ts +8 -0
- package/dist/label.js +68 -0
- package/dist/label.js.map +1 -0
- package/dist/line-chart.d.ts +155 -0
- package/dist/line-chart.js +269 -0
- package/dist/line-chart.js.map +1 -0
- package/dist/logo.d.ts +6 -0
- package/dist/logo.js +146 -0
- package/dist/logo.js.map +1 -0
- package/dist/number-input.d.ts +41 -0
- package/dist/number-input.js +418 -0
- package/dist/number-input.js.map +1 -0
- package/dist/page-header.d.ts +13 -0
- package/dist/page-header.js +113 -0
- package/dist/page-header.js.map +1 -0
- package/dist/pagination.d.ts +35 -0
- package/dist/pagination.js +352 -0
- package/dist/pagination.js.map +1 -0
- package/dist/password-input.d.ts +13 -0
- package/dist/password-input.js +356 -0
- package/dist/password-input.js.map +1 -0
- package/dist/phone-input.d.ts +21 -0
- package/dist/phone-input.js +573 -0
- package/dist/phone-input.js.map +1 -0
- package/dist/photo-gallery.d.ts +42 -0
- package/dist/photo-gallery.js +756 -0
- package/dist/photo-gallery.js.map +1 -0
- package/dist/pie-chart.d.ts +125 -0
- package/dist/pie-chart.js +192 -0
- package/dist/pie-chart.js.map +1 -0
- package/dist/popover.d.ts +20 -0
- package/dist/popover.js +169 -0
- package/dist/popover.js.map +1 -0
- package/dist/progress.d.ts +28 -0
- package/dist/progress.js +315 -0
- package/dist/progress.js.map +1 -0
- package/dist/radio-group.d.ts +32 -0
- package/dist/radio-group.js +100 -0
- package/dist/radio-group.js.map +1 -0
- package/dist/red-hat-display.css +2 -0
- package/dist/rich-text-editor.d.ts +16 -0
- package/dist/rich-text-editor.js +1000 -0
- package/dist/rich-text-editor.js.map +1 -0
- package/dist/search-input.d.ts +19 -0
- package/dist/search-input.js +411 -0
- package/dist/search-input.js.map +1 -0
- package/dist/select.d.ts +21 -0
- package/dist/select.js +305 -0
- package/dist/select.js.map +1 -0
- package/dist/separator.d.ts +10 -0
- package/dist/separator.js +72 -0
- package/dist/separator.js.map +1 -0
- package/dist/sheet.d.ts +24 -0
- package/dist/sheet.js +282 -0
- package/dist/sheet.js.map +1 -0
- package/dist/sidebar.d.ts +70 -0
- package/dist/sidebar.js +1078 -0
- package/dist/sidebar.js.map +1 -0
- package/dist/size-context.d.ts +11 -0
- package/dist/size-context.js +22 -0
- package/dist/size-context.js.map +1 -0
- package/dist/skeleton.d.ts +5 -0
- package/dist/skeleton.js +48 -0
- package/dist/skeleton.js.map +1 -0
- package/dist/sparkline.d.ts +94 -0
- package/dist/sparkline.js +568 -0
- package/dist/sparkline.js.map +1 -0
- package/dist/stepper.d.ts +43 -0
- package/dist/stepper.js +296 -0
- package/dist/stepper.js.map +1 -0
- package/dist/styles.css +2 -0
- package/dist/switch.d.ts +29 -0
- package/dist/switch.js +108 -0
- package/dist/switch.js.map +1 -0
- package/dist/tab-list.d.ts +38 -0
- package/dist/tab-list.js +394 -0
- package/dist/tab-list.js.map +1 -0
- package/dist/table.d.ts +69 -0
- package/dist/table.js +197 -0
- package/dist/table.js.map +1 -0
- package/dist/tabs.d.ts +18 -0
- package/dist/tabs.js +159 -0
- package/dist/tabs.js.map +1 -0
- package/dist/tag.d.ts +25 -0
- package/dist/tag.js +160 -0
- package/dist/tag.js.map +1 -0
- package/dist/textarea.d.ts +12 -0
- package/dist/textarea.js +78 -0
- package/dist/textarea.js.map +1 -0
- package/dist/theme/giddaa.css +2 -0
- package/dist/time-picker.d.ts +13 -0
- package/dist/time-picker.js +472 -0
- package/dist/time-picker.js.map +1 -0
- package/dist/toast.d.ts +23 -0
- package/dist/toast.js +330 -0
- package/dist/toast.js.map +1 -0
- package/dist/tooltip.d.ts +11 -0
- package/dist/tooltip.js +96 -0
- package/dist/tooltip.js.map +1 -0
- package/dist/tree-map.d.ts +127 -0
- package/dist/tree-map.js +652 -0
- package/dist/tree-map.js.map +1 -0
- package/package.json +328 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/components/ui/photo-gallery.tsx","../src/lib/cn.ts","../src/components/ui/button.tsx","../src/components/ui/size-context.tsx","../src/components/ui/carousel.tsx"],"sourcesContent":["\"use client\";\n\nimport { Dialog as DialogPrimitive } from \"@base-ui/react/dialog\";\nimport {\n\tChevronLeftIcon,\n\tChevronRightIcon,\n\tFullscreen,\n\tXIcon,\n} from \"lucide-react\";\nimport type { SVGProps } from \"react\";\nimport * as React from \"react\";\nimport { cn } from \"../../lib/cn\";\nimport { Button } from \"./button\";\nimport {\n\tCarousel,\n\ttype CarouselApi,\n\tCarouselContent,\n\tCarouselItem,\n} from \"./carousel\";\n\n/**\n * A single property photo. `src` is optional so the gallery can render a\n * \"Property Image\" placeholder in previews / while imagery is still loading.\n */\ntype PhotoGalleryImage = {\n\tsrc?: string;\n\talt?: string;\n};\n\n/**\n * Wires an Embla carousel to a controlled/uncontrolled active index. Returns\n * the `setApi` to hand to {@link Carousel} plus the live index and navigation\n * helpers. Both surfaces share this so the inline carousel and the fullscreen\n * viewer can stand alone — or stay in sync by sharing `index`/`onIndexChange`.\n */\nfunction useGalleryCarousel(\n\tindex: number | undefined,\n\tonIndexChange: ((index: number) => void) | undefined,\n) {\n\tconst [api, setApi] = React.useState<CarouselApi>();\n\tconst [selectedIndex, setSelectedIndex] = React.useState(index ?? 0);\n\tconst initialIndexRef = React.useRef(index ?? 0);\n\n\t// Keep the change callback in a ref so subscribing doesn't depend on a\n\t// caller passing a stable function identity.\n\tconst onIndexChangeRef = React.useRef(onIndexChange);\n\tReact.useEffect(() => {\n\t\tonIndexChangeRef.current = onIndexChange;\n\t});\n\n\tReact.useEffect(() => {\n\t\tif (!api) return;\n\t\tconst onSelect = () => {\n\t\t\tconst next = api.selectedScrollSnap();\n\t\t\tsetSelectedIndex(next);\n\t\t\tonIndexChangeRef.current?.(next);\n\t\t};\n\t\tonSelect();\n\t\tapi.on(\"select\", onSelect).on(\"reInit\", onSelect);\n\t\treturn () => {\n\t\t\tapi.off(\"select\", onSelect).off(\"reInit\", onSelect);\n\t\t};\n\t}, [api]);\n\n\t// Controlled mode: follow the index the parent hands down.\n\tReact.useEffect(() => {\n\t\tif (api && index !== undefined && index !== api.selectedScrollSnap()) {\n\t\t\tapi.scrollTo(index, false);\n\t\t}\n\t}, [api, index]);\n\n\tconst scrollTo = React.useCallback(\n\t\t(i: number) => api?.scrollTo(i, false),\n\t\t[api],\n\t);\n\tconst scrollPrev = React.useCallback(() => api?.scrollPrev(false), [api]);\n\tconst scrollNext = React.useCallback(() => api?.scrollNext(false), [api]);\n\n\treturn {\n\t\tsetApi,\n\t\tselectedIndex,\n\t\tinitialIndex: initialIndexRef.current,\n\t\tscrollTo,\n\t\tscrollPrev,\n\t\tscrollNext,\n\t};\n}\n\n/* -------------------------------------------------------------------------- */\n/* Shared chrome */\n/* -------------------------------------------------------------------------- */\n\n/**\n * The image surface. Fills its slide; falls back to a centred placeholder\n * label when `src` is missing. `cover` for the listing crop, `contain` for the\n * fullscreen viewer so the whole photo stays visible.\n */\nfunction GalleryFrame({\n\timage,\n\tfit = \"cover\",\n\tclassName,\n}: {\n\timage: PhotoGalleryImage | undefined;\n\tfit?: \"cover\" | \"contain\";\n\tclassName?: string;\n}) {\n\treturn (\n\t\t<div\n\t\t\tdata-slot=\"photo-gallery-frame\"\n\t\t\tclassName={cn(\n\t\t\t\t\"relative flex size-full items-center justify-center overflow-hidden\",\n\t\t\t\tclassName,\n\t\t\t)}\n\t\t>\n\t\t\t{image?.src ? (\n\t\t\t\t<img\n\t\t\t\t\tsrc={image.src}\n\t\t\t\t\talt={image.alt ?? \"Property image\"}\n\t\t\t\t\tclassName={cn(\n\t\t\t\t\t\t\"size-full\",\n\t\t\t\t\t\tfit === \"contain\" ? \"object-contain\" : \"object-cover\",\n\t\t\t\t\t)}\n\t\t\t\t\tdraggable={false}\n\t\t\t\t/>\n\t\t\t) : (\n\t\t\t\t<span className=\"text-gdt-sm font-medium text-fg-caption-placeholder\">\n\t\t\t\t\tProperty Image\n\t\t\t\t</span>\n\t\t\t)}\n\t\t</div>\n\t);\n}\n\nconst Images = (props: SVGProps<SVGSVGElement>) => (\n\t<svg\n\t\taria-hidden=\"true\"\n\t\txmlns=\"http://www.w3.org/2000/svg\"\n\t\twidth={15}\n\t\theight={16}\n\t\tviewBox=\"0 0 15 16\"\n\t\tfill=\"none\"\n\t\t{...props}\n\t>\n\t\t<path\n\t\t\tfill=\"currentColor\"\n\t\t\td=\"M6.72 14.332c-.705 0-1.38-.3-1.878-.83a2.935 2.935 0 0 1-.779-2.003V2.012l-3.02.738c-.36.102-.667.353-.853.697a1.587 1.587 0 0 0-.143 1.136l2.589 10.309c.083.318.261.598.507.798.246.2.546.309.856.31.115 0 .233-.015.349-.048l6.3-1.62H6.72ZM8.125 6c.69 0 1.25-.597 1.25-1.333 0-.735-.56-1.333-1.25-1.333s-1.25.598-1.25 1.333c0 .736.56 1.333 1.25 1.333Z\"\n\t\t/>\n\t\t<path\n\t\t\tfill=\"currentColor\"\n\t\t\td=\"M15 2a2.07 2.07 0 0 0-.55-1.414A1.818 1.818 0 0 0 13.126 0h-6.25C6.376 0 5.9.21 5.547.586A2.07 2.07 0 0 0 4.998 2v9.332c0 .53.198 1.04.55 1.414.352.375.829.586 1.326.586h6.25c.498 0 .975-.21 1.327-.586.351-.375.549-.883.549-1.414V2Zm-8.126-.667h6.25c.166 0 .325.07.443.195A.69.69 0 0 1 13.75 2v4.292l-.475-.507a1.094 1.094 0 0 0-.356-.253 1.037 1.037 0 0 0-.839 0 1.094 1.094 0 0 0-.355.253L8.749 8.96l-.787-.84a1.094 1.094 0 0 0-.356-.254 1.037 1.037 0 0 0-.839 0 1.094 1.094 0 0 0-.355.254l-.163.173V2a.69.69 0 0 1 .183-.472.606.606 0 0 1 .442-.195Z\"\n\t\t/>\n\t</svg>\n);\n\n/** Pill overlay showing `current / total`. Hidden when there is one image. */\nfunction GalleryCounter({\n\tcurrent,\n\ttotal,\n\tclassName,\n}: {\n\tcurrent: number;\n\ttotal: number;\n\tclassName?: string;\n}) {\n\tif (total <= 1) return null;\n\n\treturn (\n\t\t<div\n\t\t\tdata-slot=\"photo-gallery-counter\"\n\t\t\tclassName={cn(\n\t\t\t\t\"pointer-events-none absolute z-10 inline-flex items-center gap-1.5 rounded-full border border-line bg-surface-overlay px-2.5 py-1 text-gdt-xs font-bold text-fg-primary shadow-2\",\n\t\t\t\tclassName,\n\t\t\t)}\n\t\t>\n\t\t\t<Images aria-hidden=\"true\" className=\"size-3.5\" />\n\t\t\t<span>\n\t\t\t\t{current} / {total}\n\t\t\t</span>\n\t\t</div>\n\t);\n}\n\nfunction GalleryButton({\n\tclassName,\n\tvariant = \"ghost\",\n\t...rest\n}: React.ComponentProps<typeof Button>) {\n\treturn (\n\t\t<Button\n\t\t\tsize=\"icon\"\n\t\t\tclassName={cn(\n\t\t\t\t\"border border-line bg-surface-overlay text-fg-primary shadow-2 transition-[background-color] outline-none hover:bg-surface-raised focus-visible:border-line-focus focus-visible:shadow-[0px_0px_0px_2px_var(--color-line-focus)] size-9 sm:size-11\",\n\t\t\t\tclassName,\n\t\t\t)}\n\t\t\tvariant={variant}\n\t\t\t{...rest}\n\t\t/>\n\t);\n}\n\n/** Circular floating navigation button. Always present; the track loops. */\nfunction GalleryArrow({\n\tside,\n\tonClick,\n\tclassName,\n}: {\n\tside: \"prev\" | \"next\";\n\tonClick: () => void;\n\tclassName?: string;\n}) {\n\tconst Icon = side === \"prev\" ? ChevronLeftIcon : ChevronRightIcon;\n\treturn (\n\t\t<GalleryButton\n\t\t\ttype=\"button\"\n\t\t\tdata-slot={`photo-gallery-${side}`}\n\t\t\taria-label={side === \"prev\" ? \"Previous photo\" : \"Next photo\"}\n\t\t\tonClick={(event) => {\n\t\t\t\tevent.stopPropagation();\n\t\t\t\tonClick();\n\t\t\t}}\n\t\t\tclassName={cn(\n\t\t\t\t\"absolute top-1/2 z-10 grid -translate-y-1/2 place-items-center active:not-aria-[haspopup]:-translate-y-1/2\",\n\t\t\t\tside === \"prev\" ? \"left-2 sm:left-4\" : \"right-2 sm:right-4\",\n\t\t\t\tclassName,\n\t\t\t)}\n\t\t>\n\t\t\t<Icon className=\"cn-rtl-flip size-4 sm:size-5\" />\n\t\t</GalleryButton>\n\t);\n}\n\n/**\n * Equal-sized thumbnail strip. `cols` are sized via CSS vars so exactly N fill\n * the width (5 on mobile, more on web); extras overflow and scroll. The active\n * thumb gets a 2px focus stroke and is kept in view as the index changes.\n */\nfunction GalleryThumbnails({\n\timages,\n\tselectedIndex,\n\tonSelect,\n\tcolsClassName,\n\tclassName,\n\tactiveVariant = \"focus\",\n}: {\n\timages: PhotoGalleryImage[];\n\tselectedIndex: number;\n\tonSelect: (index: number) => void;\n\tcolsClassName: string;\n\tclassName?: string;\n\tactiveVariant?: \"focus\" | \"subtle\";\n}) {\n\tconst containerRef = React.useRef<HTMLDivElement>(null);\n\n\tReact.useEffect(() => {\n\t\tconst active = containerRef.current?.children[selectedIndex];\n\t\tactive?.scrollIntoView({\n\t\t\tbehavior: \"smooth\",\n\t\t\tinline: \"center\",\n\t\t\tblock: \"nearest\",\n\t\t});\n\t}, [selectedIndex]);\n\n\tif (images.length <= 1) return null;\n\n\treturn (\n\t\t<div\n\t\t\tref={containerRef}\n\t\t\tdata-slot=\"photo-gallery-thumbnails\"\n\t\t\tclassName={cn(\n\t\t\t\t\"flex gap-2 overflow-x-auto [--gallery-gap:0.5rem] scrollbar-none [&::-webkit-scrollbar]:hidden\",\n\t\t\t\tcolsClassName,\n\t\t\t\tclassName,\n\t\t\t)}\n\t\t>\n\t\t\t{images.map((image, index) => {\n\t\t\t\tconst isActive = index === selectedIndex;\n\t\t\t\treturn (\n\t\t\t\t\t<button\n\t\t\t\t\t\t// biome-ignore lint/suspicious/noArrayIndexKey: photos are a fixed, ordered list that never reorders, so the index is the stable identity.\n\t\t\t\t\t\tkey={index}\n\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\taria-label={`View photo ${index + 1}`}\n\t\t\t\t\t\taria-current={isActive}\n\t\t\t\t\t\tonClick={() => onSelect(index)}\n\t\t\t\t\t\tstyle={{\n\t\t\t\t\t\t\twidth:\n\t\t\t\t\t\t\t\t\"calc((100% - (var(--gallery-cols) - 1) * var(--gallery-gap)) / var(--gallery-cols))\",\n\t\t\t\t\t\t}}\n\t\t\t\t\t\tclassName={cn(\n\t\t\t\t\t\t\t\"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)]\",\n\t\t\t\t\t\t\tactiveVariant === \"subtle\" &&\n\t\t\t\t\t\t\t\t\"focus-visible:border-line-subtle focus-visible:shadow-[0px_0px_0px_2px_var(--color-line-subtle)]\",\n\t\t\t\t\t\t\tisActive\n\t\t\t\t\t\t\t\t? activeVariant === \"subtle\"\n\t\t\t\t\t\t\t\t\t? \"border-line-subtle\"\n\t\t\t\t\t\t\t\t\t: \"border-line-focus\"\n\t\t\t\t\t\t\t\t: \"border-transparent opacity-80 hover:opacity-100\",\n\t\t\t\t\t\t)}\n\t\t\t\t\t>\n\t\t\t\t\t\t{image.src ? (\n\t\t\t\t\t\t\t<img\n\t\t\t\t\t\t\t\tsrc={image.src}\n\t\t\t\t\t\t\t\talt={image.alt ?? `Property image ${index + 1}`}\n\t\t\t\t\t\t\t\tclassName=\"size-full object-cover\"\n\t\t\t\t\t\t\t\tdraggable={false}\n\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t) : null}\n\t\t\t\t\t</button>\n\t\t\t\t);\n\t\t\t})}\n\t\t</div>\n\t);\n}\n\n/* -------------------------------------------------------------------------- */\n/* Inline carousel */\n/* -------------------------------------------------------------------------- */\n\ntype PhotoGalleryProps = Omit<React.ComponentProps<\"div\">, \"onClick\"> & {\n\timages: PhotoGalleryImage[];\n\t/** Controlled active index. Omit to let the gallery manage it internally. */\n\tindex?: number;\n\tonIndexChange?: (index: number) => void;\n\t/** Called when the hero image is activated — wire this to open fullscreen. */\n\tonImageClick?: (index: number) => void;\n};\n\n/**\n * Inline property gallery: a swipeable hero image (drag / touch / ← →) with a\n * counter pill, navigation arrows, and a thumbnail strip below. Built on\n * {@link Carousel}. Standalone — pass `onImageClick` to open it into a\n * {@link PhotoGalleryFullscreen} viewer, but neither surface needs the other.\n */\nfunction PhotoGallery({\n\timages,\n\tindex,\n\tonIndexChange,\n\tonImageClick,\n\tclassName,\n\t...props\n}: PhotoGalleryProps) {\n\tconst {\n\t\tsetApi,\n\t\tselectedIndex,\n\t\tinitialIndex,\n\t\tscrollTo,\n\t\tscrollPrev,\n\t\tscrollNext,\n\t} = useGalleryCarousel(index, onIndexChange);\n\tconst hasMany = images.length > 1;\n\n\treturn (\n\t\t<div\n\t\t\tdata-slot=\"photo-gallery\"\n\t\t\tclassName={cn(\n\t\t\t\t\"flex w-full flex-col gap-3 rounded-2xl border border-line bg-surface p-3\",\n\t\t\t\tclassName,\n\t\t\t)}\n\t\t\t{...props}\n\t\t>\n\t\t\t<Carousel\n\t\t\t\timageSize=\"full\"\n\t\t\t\tsetApi={setApi}\n\t\t\t\topts={{ loop: true, startIndex: initialIndex, watchDrag: hasMany }}\n\t\t\t\tclassName=\"aspect-video rounded-xl bg-surface-raised\"\n\t\t\t>\n\t\t\t\t<CarouselContent>\n\t\t\t\t\t{images.map((image, i) => (\n\t\t\t\t\t\t<CarouselItem\n\t\t\t\t\t\t\t// biome-ignore lint/suspicious/noArrayIndexKey: photos are a fixed, ordered list that never reorders, so the index is the stable identity.\n\t\t\t\t\t\t\tkey={i}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t<GalleryFrame image={image} />\n\t\t\t\t\t\t</CarouselItem>\n\t\t\t\t\t))}\n\t\t\t\t</CarouselContent>\n\t\t\t\t{onImageClick && (\n\t\t\t\t\t<GalleryButton\n\t\t\t\t\t\tclassName=\"absolute bottom-2 right-2 sm:bottom-4 sm:right-4\"\n\t\t\t\t\t\tonClick={() => onImageClick(selectedIndex)}\n\t\t\t\t\t>\n\t\t\t\t\t\t<Fullscreen className=\"size-4 md:size-5\" />\n\t\t\t\t\t</GalleryButton>\n\t\t\t\t)}\n\n\t\t\t\t<GalleryCounter\n\t\t\t\t\tcurrent={selectedIndex + 1}\n\t\t\t\t\ttotal={images.length}\n\t\t\t\t\tclassName=\"top-2 right-2 sm:top-4 md:right-4\"\n\t\t\t\t/>\n\t\t\t\t{hasMany && (\n\t\t\t\t\t<>\n\t\t\t\t\t\t<GalleryArrow side=\"prev\" onClick={scrollPrev} />\n\t\t\t\t\t\t<GalleryArrow side=\"next\" onClick={scrollNext} />\n\t\t\t\t\t</>\n\t\t\t\t)}\n\t\t\t</Carousel>\n\n\t\t\t<GalleryThumbnails\n\t\t\t\timages={images}\n\t\t\t\tselectedIndex={selectedIndex}\n\t\t\t\tonSelect={scrollTo}\n\t\t\t\tcolsClassName=\"[--gallery-cols:5] sm:[--gallery-cols:8]\"\n\t\t\t/>\n\t\t</div>\n\t);\n}\n\n/* -------------------------------------------------------------------------- */\n/* Fullscreen viewer */\n/* -------------------------------------------------------------------------- */\n\ntype PhotoGalleryFullscreenProps = {\n\timages: PhotoGalleryImage[];\n\topen: boolean;\n\tonOpenChange: (open: boolean) => void;\n\t/** Controlled active index. Omit to let the viewer manage it internally. */\n\tindex?: number;\n\tonIndexChange?: (index: number) => void;\n};\n\n/**\n * Fullscreen lightbox: takes over the viewport over a `bg-canvas` surface for\n * distraction-free browsing. Swipeable (drag / touch) with ← → key navigation\n * and ESC to close. Built on {@link Carousel}; controlled via `open` /\n * `onOpenChange` and usable without the inline gallery.\n */\nfunction PhotoGalleryFullscreen({\n\timages,\n\topen,\n\tonOpenChange,\n\tindex,\n\tonIndexChange,\n}: PhotoGalleryFullscreenProps) {\n\tconst {\n\t\tsetApi,\n\t\tselectedIndex,\n\t\tinitialIndex,\n\t\tscrollTo,\n\t\tscrollPrev,\n\t\tscrollNext,\n\t} = useGalleryCarousel(index, onIndexChange);\n\tconst hasMany = images.length > 1;\n\n\tconst handleKeyDown = (event: React.KeyboardEvent<HTMLDivElement>) => {\n\t\t// The carousel handles arrows in the capture phase when it holds focus;\n\t\t// skip so focus inside the track doesn't navigate twice.\n\t\tif (event.defaultPrevented) return;\n\t\tif (event.key === \"ArrowLeft\") {\n\t\t\tevent.preventDefault();\n\t\t\tscrollPrev();\n\t\t} else if (event.key === \"ArrowRight\") {\n\t\t\tevent.preventDefault();\n\t\t\tscrollNext();\n\t\t}\n\t};\n\n\treturn (\n\t\t<DialogPrimitive.Root open={open} onOpenChange={onOpenChange}>\n\t\t\t<DialogPrimitive.Portal>\n\t\t\t\t<DialogPrimitive.Backdrop\n\t\t\t\t\tdata-slot=\"photo-gallery-fullscreen-backdrop\"\n\t\t\t\t\tclassName=\"fixed inset-0 z-50 bg-black/50 transition-opacity duration-150 data-closed:opacity-0 data-open:opacity-100\"\n\t\t\t\t/>\n\t\t\t\t<DialogPrimitive.Popup\n\t\t\t\t\tdata-slot=\"photo-gallery-fullscreen\"\n\t\t\t\t\tonKeyDown={handleKeyDown}\n\t\t\t\t\tclassName=\"fixed inset-0 z-50 flex flex-col gap-4 p-4 sm:p-6 xl:p-8 outline-none transition-opacity duration-150 data-closed:opacity-0 data-open:opacity-100\"\n\t\t\t\t>\n\t\t\t\t\t<DialogPrimitive.Title className=\"sr-only\">\n\t\t\t\t\t\tPhoto Gallery\n\t\t\t\t\t</DialogPrimitive.Title>\n\n\t\t\t\t\t<div className=\"w-full flex items-center justify-between\">\n\t\t\t\t\t\t<GalleryCounter\n\t\t\t\t\t\t\tcurrent={selectedIndex + 1}\n\t\t\t\t\t\t\ttotal={images.length}\n\t\t\t\t\t\t\tclassName=\"static\"\n\t\t\t\t\t\t/>\n\t\t\t\t\t\t<DialogPrimitive.Close\n\t\t\t\t\t\t\tdata-slot=\"photo-gallery-fullscreen-close\"\n\t\t\t\t\t\t\taria-label=\"Close gallery\"\n\t\t\t\t\t\t\trender={<GalleryButton />}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t<XIcon className=\"size-4 sm:size-5\" />\n\t\t\t\t\t\t</DialogPrimitive.Close>\n\t\t\t\t\t</div>\n\n\t\t\t\t\t<Carousel\n\t\t\t\t\t\timageSize=\"full\"\n\t\t\t\t\t\tsetApi={setApi}\n\t\t\t\t\t\topts={{ loop: true, startIndex: initialIndex, watchDrag: hasMany }}\n\t\t\t\t\t\tclassName=\"flex-1 w-full rounded-xl bg-transparent\"\n\t\t\t\t\t>\n\t\t\t\t\t\t<CarouselContent className=\"items-center\">\n\t\t\t\t\t\t\t{images.map((image, i) => (\n\t\t\t\t\t\t\t\t<CarouselItem\n\t\t\t\t\t\t\t\t\t// biome-ignore lint/suspicious/noArrayIndexKey: photos are a fixed, ordered list that never reorders, so the index is the stable identity.\n\t\t\t\t\t\t\t\t\tkey={i}\n\t\t\t\t\t\t\t\t\tclassName=\"h-full\"\n\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t<GalleryFrame image={image} fit=\"contain\" />\n\t\t\t\t\t\t\t\t</CarouselItem>\n\t\t\t\t\t\t\t))}\n\t\t\t\t\t\t</CarouselContent>\n\t\t\t\t\t\t{hasMany && (\n\t\t\t\t\t\t\t<>\n\t\t\t\t\t\t\t\t<GalleryArrow\n\t\t\t\t\t\t\t\t\tside=\"prev\"\n\t\t\t\t\t\t\t\t\tonClick={scrollPrev}\n\t\t\t\t\t\t\t\t\tclassName=\"left-0!\"\n\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t\t<GalleryArrow\n\t\t\t\t\t\t\t\t\tside=\"next\"\n\t\t\t\t\t\t\t\t\tonClick={scrollNext}\n\t\t\t\t\t\t\t\t\tclassName=\"right-0!\"\n\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t</>\n\t\t\t\t\t\t)}\n\t\t\t\t\t</Carousel>\n\n\t\t\t\t\t{hasMany && (\n\t\t\t\t\t\t<div className=\"flex flex-col gap-3\">\n\t\t\t\t\t\t\t<p className=\"hidden text-center text-gdt-xs text-fg-secondary sm:block\">\n\t\t\t\t\t\t\t\t← → to navigate · ESC to close\n\t\t\t\t\t\t\t</p>\n\t\t\t\t\t\t\t<GalleryThumbnails\n\t\t\t\t\t\t\t\timages={images}\n\t\t\t\t\t\t\t\tselectedIndex={selectedIndex}\n\t\t\t\t\t\t\t\tonSelect={scrollTo}\n\t\t\t\t\t\t\t\tcolsClassName=\"[--gallery-cols:5] sm:[--gallery-cols:10]\"\n\t\t\t\t\t\t\t\tclassName=\"mx-auto w-full max-w-3xl\"\n\t\t\t\t\t\t\t\tactiveVariant=\"subtle\"\n\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t)}\n\t\t\t\t</DialogPrimitive.Popup>\n\t\t\t</DialogPrimitive.Portal>\n\t\t</DialogPrimitive.Root>\n\t);\n}\n\nexport {\n\ttype PhotoGalleryImage,\n\ttype PhotoGalleryProps,\n\ttype PhotoGalleryFullscreenProps,\n\tPhotoGallery,\n\tPhotoGalleryFullscreen,\n};\n","import { type ClassValue, clsx } from \"clsx\";\nimport { extendTailwindMerge } from \"tailwind-merge\";\n\nconst twMerge = extendTailwindMerge({\n\textend: {\n\t\tclassGroups: {\n\t\t\t\"font-size\": [\n\t\t\t\t{\n\t\t\t\t\ttext: [\n\t\t\t\t\t\t\"gdt-display\",\n\t\t\t\t\t\t\"gdt-h1\",\n\t\t\t\t\t\t\"gdt-h2\",\n\t\t\t\t\t\t\"gdt-h3\",\n\t\t\t\t\t\t\"gdt-h4\",\n\t\t\t\t\t\t\"gdt-h5\",\n\t\t\t\t\t\t\"gdt-xl\",\n\t\t\t\t\t\t\"gdt-lg\",\n\t\t\t\t\t\t\"gdt-md\",\n\t\t\t\t\t\t\"gdt-sm\",\n\t\t\t\t\t\t\"gdt-xs\",\n\t\t\t\t\t\t\"gdt-subtext\",\n\t\t\t\t\t\t\"gdt-capitalized\",\n\t\t\t\t\t],\n\t\t\t\t},\n\t\t\t],\n\t\t},\n\t},\n});\n\nexport function cn(...inputs: ClassValue[]) {\n\treturn twMerge(clsx(inputs));\n}\n","import { Button as ButtonPrimitive } from \"@base-ui/react/button\";\nimport { cva, type VariantProps } from \"class-variance-authority\";\nimport { Loader } from \"lucide-react\";\nimport type * as React from \"react\";\nimport { cn } from \"../../lib/cn\";\nimport { useComponentSize } from \"./size-context\";\n\nconst buttonVariants = cva(\n\t\"group/button inline-flex shrink-0 items-center justify-center rounded-full bg-clip-padding whitespace-nowrap font-bold transition-[background-color,color,border-color,box-shadow,transform] outline-none select-none focus-visible:border-line-focus focus-visible:shadow-[0px_0px_0px_2px_var(--color-line-focus)] active:not-aria-[haspopup]:translate-y-px disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50 aria-invalid:border-line-danger aria-invalid:ring-3 aria-invalid:ring-destructive/20 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4\",\n\t{\n\t\tvariants: {\n\t\t\tvariant: {\n\t\t\t\tprimary:\n\t\t\t\t\t\"bg-action-primary text-fg-on-brand hover:bg-action-primary-hover active:bg-action-primary-pressed\",\n\t\t\t\t\"primary-outline\":\n\t\t\t\t\t\"border-2 border-action-primary bg-transparent text-fg-brand hover:bg-surface-brand-subtle active:bg-surface-brand-subtle data-focused:bg-surface-brand-subtle data-pressed:bg-surface-brand-subtle\",\n\t\t\t\tsecondary:\n\t\t\t\t\t\"bg-surface-brand-subtle text-fg-brand hover:bg-action-primary-hover hover:text-fg-on-brand active:bg-action-primary-pressed active:text-fg-on-brand data-focused:bg-action-primary-hover data-pressed:bg-action-primary-pressed data-focused:text-fg-on-brand data-pressed:text-fg-on-brand\",\n\t\t\t\t\"secondary-outline\":\n\t\t\t\t\t\"border-2 border-action-secondary bg-transparent text-fg-brand hover:bg-surface-brand-subtle active:bg-surface-brand-subtle data-focused:bg-surface-brand-subtle data-pressed:bg-surface-brand-subtle\",\n\t\t\t\ttertiary:\n\t\t\t\t\t\"bg-action-tertiary text-fg-primary hover:bg-action-tertiary-hover active:bg-action-tertiary-pressed data-focused:bg-action-tertiary-hover data-pressed:bg-action-tertiary-pressed\",\n\t\t\t\t\"tertiary-outline\":\n\t\t\t\t\t\"border border-line-strong bg-transparent text-fg-primary hover:bg-surface active:bg-surface-raised data-focused:bg-surface data-pressed:bg-surface-raised\",\n\t\t\t\tghost:\n\t\t\t\t\t\"bg-transparent text-fg-primary hover:bg-surface active:bg-surface-raised data-focused:bg-surface data-pressed:bg-surface-raised\",\n\t\t\t\tdanger:\n\t\t\t\t\t\"bg-action-danger text-fg-on-brand hover:bg-action-danger-hover active:bg-action-danger-pressed data-focused:bg-action-danger-hover data-pressed:bg-action-danger-pressed\",\n\t\t\t\t\"glass-overlay\": \"bg-surface-glass-overlay-button text-fg-on-brand\",\n\t\t\t\t\"link-brand\":\n\t\t\t\t\t\"border-transparent bg-transparent px-0 text-fg-brand hover:underline active:underline active:opacity-70\",\n\t\t\t\t\"link-neutral\":\n\t\t\t\t\t\"border-transparent bg-transparent px-0 text-fg-secondary hover:underline active:underline active:opacity-70\",\n\t\t\t},\n\t\t\tsize: {\n\t\t\t\tsm: \"h-8 gap-2 px-3.5 text-gdt-xs has-data-[icon=inline-end]:pr-3 has-data-[icon=inline-start]:pl-3 [&_svg:not([class*='size-'])]:size-3.5\",\n\t\t\t\tmd: \"h-10 gap-2 px-4.5 text-gdt-sm has-data-[icon=inline-end]:pr-4 has-data-[icon=inline-start]:pl-4\",\n\t\t\t\tlg: \"h-12.5 gap-2.5 px-5.5 text-gdt-md has-data-[icon=inline-end]:pr-5 has-data-[icon=inline-start]:pl-5 [&_svg:not([class*='size-'])]:size-4.5\",\n\t\t\t\txl: \"h-14 gap-3 px-6.5 text-gdt-lg has-data-[icon=inline-end]:pr-6 has-data-[icon=inline-start]:pl-6 [&_svg:not([class*='size-'])]:size-5\",\n\t\t\t\ticon: \"size-10\",\n\t\t\t\t\"icon-xs\": \"size-6 text-gdt-xs [&_svg:not([class*='size-'])]:size-3\",\n\t\t\t\t\"icon-sm\": \"size-8 text-gdt-sm [&_svg:not([class*='size-'])]:size-3.5\",\n\t\t\t\t\"icon-lg\":\n\t\t\t\t\t\"size-12.5 text-gdt-md [&_svg:not([class*='size-'])]:size-4.5\",\n\t\t\t\t\"icon-xl\": \"size-14 text-gdt-lg [&_svg:not([class*='size-'])]:size-5\",\n\t\t\t},\n\t\t},\n\t\tdefaultVariants: {\n\t\t\tvariant: \"primary\",\n\t\t\tsize: \"md\",\n\t\t},\n\t},\n);\n\ntype ButtonSize = NonNullable<VariantProps<typeof buttonVariants>[\"size\"]>;\n\nfunction Button({\n\tclassName,\n\tvariant = \"primary\",\n\tsize,\n\tisLoading = false,\n\tchildren,\n\tdisabled = false,\n\t...props\n}: ButtonPrimitive.Props &\n\tVariantProps<typeof buttonVariants> & { isLoading?: boolean }) {\n\tconst inheritedSize = useComponentSize();\n\tconst resolvedSize = size ?? inheritedSize;\n\tconst _disabled = disabled || isLoading;\n\treturn (\n\t\t<ButtonPrimitive\n\t\t\tdata-slot=\"button\"\n\t\t\tclassName={cn(buttonVariants({ variant, size: resolvedSize, className }))}\n\t\t\tdisabled={_disabled}\n\t\t\t{...props}\n\t\t>\n\t\t\t{isLoading && <Loader className=\"size-4 animate-spin\" />} {children}\n\t\t</ButtonPrimitive>\n\t);\n}\n\ntype ButtonLinkProps = Omit<React.ComponentProps<\"a\">, \"href\"> &\n\tVariantProps<typeof buttonVariants> & {\n\t\tdisabled?: boolean;\n\t\thref: string;\n\t};\n\nfunction ButtonLink({\n\tclassName,\n\thref,\n\tvariant = \"primary\",\n\tsize,\n\tdisabled = false,\n\tonClick,\n\t...props\n}: ButtonLinkProps) {\n\tconst inheritedSize = useComponentSize();\n\tconst resolvedSize: ButtonSize = size ?? inheritedSize;\n\n\treturn (\n\t\t<a\n\t\t\tdata-slot=\"button-link\"\n\t\t\thref={href}\n\t\t\taria-disabled={disabled}\n\t\t\ttabIndex={disabled ? -1 : props.tabIndex}\n\t\t\tclassName={cn(buttonVariants({ variant, size: resolvedSize, className }))}\n\t\t\tonClick={(event) => {\n\t\t\t\tif (disabled) {\n\t\t\t\t\tevent.preventDefault();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tonClick?.(event);\n\t\t\t}}\n\t\t\t{...props}\n\t\t/>\n\t);\n}\n\nexport { Button, ButtonLink, buttonVariants };\n","\"use client\";\n\nimport { createContext, type ReactNode, useContext } from \"react\";\n\ntype ComponentSize = \"sm\" | \"md\" | \"lg\";\n\nconst SizeContext = createContext<ComponentSize | undefined>(undefined);\n\nfunction SizeProvider({\n\tchildren,\n\tsize,\n}: {\n\tchildren: ReactNode;\n\tsize?: ComponentSize | null;\n}) {\n\tif (!size) {\n\t\treturn <>{children}</>;\n\t}\n\n\treturn <SizeContext.Provider value={size}>{children}</SizeContext.Provider>;\n}\n\nfunction useComponentSize(\n\tsize?: ComponentSize | null,\n\tfallback: ComponentSize = \"md\",\n) {\n\tconst inheritedSize = useContext(SizeContext);\n\treturn size ?? inheritedSize ?? fallback;\n}\n\nexport { SizeProvider, useComponentSize, type ComponentSize };\n","\"use client\";\n\nimport { cva, type VariantProps } from \"class-variance-authority\";\nimport useEmblaCarousel, {\n\ttype UseEmblaCarouselType,\n} from \"embla-carousel-react\";\nimport { ChevronLeftIcon, ChevronRightIcon } from \"lucide-react\";\nimport * as React from \"react\";\nimport { cn } from \"../../lib/cn\";\nimport { type ComponentSize, useComponentSize } from \"./size-context\";\n\ntype CarouselApi = UseEmblaCarouselType[1];\ntype UseCarouselParameters = Parameters<typeof useEmblaCarousel>;\ntype CarouselOptions = UseCarouselParameters[0];\ntype CarouselPlugin = UseCarouselParameters[1];\n\n/**\n * Shared glassmorphic treatment for every overlay surface in the carousel\n * (navigation arrows + indicator). Dark glass at 35% over a 16px backdrop\n * blur with a near-invisible 18% light border that lifts the chrome off the\n * imagery. Always dark, in both themes — see `--color-surface-glass-overlay-button`.\n */\nconst glassChrome =\n\t\"border border-fg-on-brand/[0.18] bg-surface-glass-overlay-button backdrop-blur-[16px]\";\n\nconst carouselVariants = cva(\n\t\"relative overflow-hidden rounded-2xl bg-surface-brand-subtle\",\n\t{\n\t\tvariants: {\n\t\t\timageSize: {\n\t\t\t\tsm: \"w-[320px] max-w-full aspect-[320/220]\",\n\t\t\t\tmd: \"w-[520px] max-w-full aspect-[520/340]\",\n\t\t\t\tlg: \"w-[800px] max-w-full aspect-[800/500]\",\n\t\t\t\tfull: \"w-full\",\n\t\t\t},\n\t\t},\n\t\tdefaultVariants: {\n\t\t\timageSize: \"md\",\n\t\t},\n\t},\n);\n\ntype CarouselContextProps = {\n\tcarouselRef: ReturnType<typeof useEmblaCarousel>[0];\n\tapi: CarouselApi;\n\torientation: \"horizontal\" | \"vertical\";\n\tsize: ComponentSize;\n\tscrollPrev: () => void;\n\tscrollNext: () => void;\n\tscrollTo: (index: number) => void;\n\tcanScrollPrev: boolean;\n\tcanScrollNext: boolean;\n\tselectedIndex: number;\n\tscrollSnaps: number[];\n};\n\nconst CarouselContext = React.createContext<CarouselContextProps | null>(null);\n\nfunction useCarousel() {\n\tconst context = React.useContext(CarouselContext);\n\n\tif (!context) {\n\t\tthrow new Error(\"useCarousel must be used within a <Carousel />\");\n\t}\n\n\treturn context;\n}\n\ntype CarouselProps = {\n\topts?: CarouselOptions;\n\tplugins?: CarouselPlugin;\n\torientation?: \"horizontal\" | \"vertical\";\n\t/** Scales the chrome (arrows + indicator) without touching the image. */\n\tsize?: ComponentSize;\n\tsetApi?: (api: CarouselApi) => void;\n};\n\nfunction Carousel({\n\torientation = \"horizontal\",\n\topts,\n\tsetApi,\n\tplugins,\n\tsize,\n\timageSize,\n\tclassName,\n\tchildren,\n\t...props\n}: React.ComponentProps<\"div\"> &\n\tCarouselProps &\n\tVariantProps<typeof carouselVariants>) {\n\tconst resolvedSize = useComponentSize(size);\n\tconst [carouselRef, api] = useEmblaCarousel(\n\t\t{\n\t\t\t...opts,\n\t\t\taxis: orientation === \"horizontal\" ? \"x\" : \"y\",\n\t\t},\n\t\tplugins,\n\t);\n\tconst [canScrollPrev, setCanScrollPrev] = React.useState(false);\n\tconst [canScrollNext, setCanScrollNext] = React.useState(false);\n\tconst [selectedIndex, setSelectedIndex] = React.useState(0);\n\tconst [scrollSnaps, setScrollSnaps] = React.useState<number[]>([]);\n\n\tconst onSelect = React.useCallback((api: CarouselApi) => {\n\t\tif (!api) return;\n\t\tsetCanScrollPrev(api.canScrollPrev());\n\t\tsetCanScrollNext(api.canScrollNext());\n\t\tsetSelectedIndex(api.selectedScrollSnap());\n\t}, []);\n\n\tconst onReInit = React.useCallback(\n\t\t(api: CarouselApi) => {\n\t\t\tif (!api) return;\n\t\t\tsetScrollSnaps(api.scrollSnapList());\n\t\t\tonSelect(api);\n\t\t},\n\t\t[onSelect],\n\t);\n\n\tconst scrollPrev = React.useCallback(() => {\n\t\tapi?.scrollPrev();\n\t}, [api]);\n\n\tconst scrollNext = React.useCallback(() => {\n\t\tapi?.scrollNext();\n\t}, [api]);\n\n\tconst scrollTo = React.useCallback(\n\t\t(index: number) => {\n\t\t\tapi?.scrollTo(index);\n\t\t},\n\t\t[api],\n\t);\n\n\tconst handleKeyDown = React.useCallback(\n\t\t(event: React.KeyboardEvent<HTMLDivElement>) => {\n\t\t\tif (event.key === \"ArrowLeft\") {\n\t\t\t\tevent.preventDefault();\n\t\t\t\tscrollPrev();\n\t\t\t} else if (event.key === \"ArrowRight\") {\n\t\t\t\tevent.preventDefault();\n\t\t\t\tscrollNext();\n\t\t\t}\n\t\t},\n\t\t[scrollPrev, scrollNext],\n\t);\n\n\tReact.useEffect(() => {\n\t\tif (!api || !setApi) return;\n\t\tsetApi(api);\n\t}, [api, setApi]);\n\n\tReact.useEffect(() => {\n\t\tif (!api) return;\n\t\tonReInit(api);\n\t\tapi.on(\"reInit\", onReInit);\n\t\tapi.on(\"select\", onSelect);\n\n\t\treturn () => {\n\t\t\tapi?.off(\"reInit\", onReInit);\n\t\t\tapi?.off(\"select\", onSelect);\n\t\t};\n\t}, [api, onReInit, onSelect]);\n\n\treturn (\n\t\t<CarouselContext.Provider\n\t\t\tvalue={{\n\t\t\t\tcarouselRef,\n\t\t\t\tapi,\n\t\t\t\torientation,\n\t\t\t\tsize: resolvedSize,\n\t\t\t\tscrollPrev,\n\t\t\t\tscrollNext,\n\t\t\t\tscrollTo,\n\t\t\t\tcanScrollPrev,\n\t\t\t\tcanScrollNext,\n\t\t\t\tselectedIndex,\n\t\t\t\tscrollSnaps,\n\t\t\t}}\n\t\t>\n\t\t\t{/* biome-ignore lint/a11y/useSemanticElements: the W3C carousel pattern is a region with aria-roledescription, not a generic landmark element. */}\n\t\t\t<div\n\t\t\t\tonKeyDownCapture={handleKeyDown}\n\t\t\t\tclassName={cn(carouselVariants({ imageSize }), className)}\n\t\t\t\trole=\"region\"\n\t\t\t\taria-roledescription=\"carousel\"\n\t\t\t\tdata-slot=\"carousel\"\n\t\t\t\t{...props}\n\t\t\t>\n\t\t\t\t{children}\n\t\t\t</div>\n\t\t</CarouselContext.Provider>\n\t);\n}\n\nfunction CarouselContent({ className, ...props }: React.ComponentProps<\"div\">) {\n\tconst { carouselRef, orientation } = useCarousel();\n\n\treturn (\n\t\t<div\n\t\t\tref={carouselRef}\n\t\t\tclassName=\"h-full overflow-hidden\"\n\t\t\tdata-slot=\"carousel-content\"\n\t\t>\n\t\t\t<div\n\t\t\t\tclassName={cn(\n\t\t\t\t\t\"flex h-full\",\n\t\t\t\t\torientation === \"horizontal\" ? \"\" : \"flex-col\",\n\t\t\t\t\tclassName,\n\t\t\t\t)}\n\t\t\t\t{...props}\n\t\t\t/>\n\t\t</div>\n\t);\n}\n\nfunction CarouselItem({ className, ...props }: React.ComponentProps<\"div\">) {\n\treturn (\n\t\t// biome-ignore lint/a11y/useSemanticElements: a carousel slide is a role=\"group\" with aria-roledescription per the W3C pattern, not a fieldset.\n\t\t<div\n\t\t\trole=\"group\"\n\t\t\taria-roledescription=\"slide\"\n\t\t\tdata-slot=\"carousel-item\"\n\t\t\tclassName={cn(\"min-w-0 shrink-0 grow-0 basis-full\", className)}\n\t\t\t{...props}\n\t\t/>\n\t);\n}\n\nconst arrowVariants = cva(\n\tcn(\n\t\t\"absolute top-1/2 z-10 grid -translate-y-1/2 place-items-center rounded-full text-fg-on-brand/95 shadow-1 transition-[transform,opacity] outline-none\",\n\t\tglassChrome,\n\t\t\"hover:scale-105 focus-visible:shadow-[0px_0px_0px_2px_var(--color-line-focus)] active:scale-95 disabled:pointer-events-none disabled:opacity-40\",\n\t),\n\t{\n\t\tvariants: {\n\t\t\tsize: {\n\t\t\t\tsm: \"size-8 [&_svg]:size-4\",\n\t\t\t\tmd: \"size-10 [&_svg]:size-[18px]\",\n\t\t\t\tlg: \"size-12 [&_svg]:size-5\",\n\t\t\t},\n\t\t\tside: {\n\t\t\t\tprev: \"\",\n\t\t\t\tnext: \"\",\n\t\t\t},\n\t\t},\n\t\tcompoundVariants: [\n\t\t\t{ size: \"sm\", side: \"prev\", className: \"left-3\" },\n\t\t\t{ size: \"sm\", side: \"next\", className: \"right-3\" },\n\t\t\t{ size: \"md\", side: \"prev\", className: \"left-4\" },\n\t\t\t{ size: \"md\", side: \"next\", className: \"right-4\" },\n\t\t\t{ size: \"lg\", side: \"prev\", className: \"left-5\" },\n\t\t\t{ size: \"lg\", side: \"next\", className: \"right-5\" },\n\t\t],\n\t\tdefaultVariants: {\n\t\t\tsize: \"md\",\n\t\t\tside: \"prev\",\n\t\t},\n\t},\n);\n\nfunction CarouselPrevious({\n\tclassName,\n\t...props\n}: React.ComponentProps<\"button\">) {\n\tconst { size, scrollPrev, canScrollPrev } = useCarousel();\n\n\treturn (\n\t\t<button\n\t\t\ttype=\"button\"\n\t\t\tdata-slot=\"carousel-previous\"\n\t\t\tclassName={cn(arrowVariants({ size, side: \"prev\" }), className)}\n\t\t\tdisabled={!canScrollPrev}\n\t\t\tonClick={scrollPrev}\n\t\t\t{...props}\n\t\t>\n\t\t\t<ChevronLeftIcon className=\"cn-rtl-flip\" />\n\t\t\t<span className=\"sr-only\">Previous slide</span>\n\t\t</button>\n\t);\n}\n\nfunction CarouselNext({ className, ...props }: React.ComponentProps<\"button\">) {\n\tconst { size, scrollNext, canScrollNext } = useCarousel();\n\n\treturn (\n\t\t<button\n\t\t\ttype=\"button\"\n\t\t\tdata-slot=\"carousel-next\"\n\t\t\tclassName={cn(arrowVariants({ size, side: \"next\" }), className)}\n\t\t\tdisabled={!canScrollNext}\n\t\t\tonClick={scrollNext}\n\t\t\t{...props}\n\t\t>\n\t\t\t<ChevronRightIcon className=\"cn-rtl-flip\" />\n\t\t\t<span className=\"sr-only\">Next slide</span>\n\t\t</button>\n\t);\n}\n\n/**\n * Past this many items the indicator switches from \"show every dot\" to the\n * Airbnb-style windowed mode: at most `MANY_WINDOW` dots, the active one kept\n * near the centre, and dots at a clipped edge faded down to imply more beyond.\n */\nconst MANY_THRESHOLD = 5;\nconst MANY_WINDOW = 5;\n\ntype IndicatorDot = {\n\t/** Index into the full list of slides. */\n\tindex: number;\n\t/** True for the outermost dot on a side that has hidden slides beyond it. */\n\tfaded: boolean;\n};\n\n/**\n * Returns the dots to render plus the window start, so the determinate and\n * windowed (\"many\") cases share one render path. The dot strip is always the\n * full list; `start` drives the translate that slides it under the viewport.\n */\nfunction getIndicatorWindow(count: number, selected: number) {\n\tif (count < MANY_THRESHOLD) {\n\t\tconst dots: IndicatorDot[] = Array.from({ length: count }, (_, index) => ({\n\t\t\tindex,\n\t\t\tfaded: false,\n\t\t}));\n\t\treturn { dots, start: 0, visible: count };\n\t}\n\n\tconst half = Math.floor(MANY_WINDOW / 2);\n\tconst start = Math.max(0, Math.min(selected - half, count - MANY_WINDOW));\n\tconst end = start + MANY_WINDOW - 1;\n\tconst overflowLeft = start > 0;\n\tconst overflowRight = end < count - 1;\n\n\tconst dots: IndicatorDot[] = Array.from({ length: count }, (_, index) => ({\n\t\tindex,\n\t\tfaded:\n\t\t\t(overflowLeft && index === start) || (overflowRight && index === end),\n\t}));\n\n\treturn { dots, start, visible: MANY_WINDOW };\n}\n\n/** Dot diameter, gap, and pill padding per size — in px to keep them crisp. */\nconst INDICATOR_SIZES: Record<\n\tComponentSize,\n\t{ dot: number; gap: number; padX: number; padY: number }\n> = {\n\tsm: { dot: 5, gap: 4, padX: 8, padY: 4 },\n\tmd: { dot: 7, gap: 5, padX: 10, padY: 5 },\n\tlg: { dot: 9, gap: 6, padX: 12, padY: 6 },\n};\n\n/** Distance of the indicator from the bottom edge, per size (12/18/24px). */\nconst INDICATOR_OFFSET: Record<ComponentSize, string> = {\n\tsm: \"bottom-3\",\n\tmd: \"bottom-[18px]\",\n\tlg: \"bottom-6\",\n};\n\ntype CarouselIndicatorProps = Omit<\n\tReact.ComponentProps<\"div\">,\n\t\"children\" | \"onSelect\"\n> & {\n\t/** Override the live count (e.g. for static design previews). */\n\tcount?: number;\n\t/** Override the live selected index. */\n\tselected?: number;\n\t/** Override the inherited carousel size. */\n\tsize?: ComponentSize;\n\t/** Jump to a slide when a dot is activated. Defaults to the carousel API. */\n\tonSelect?: (index: number) => void;\n};\n\nfunction CarouselIndicator({\n\tclassName,\n\tcount: countProp,\n\tselected: selectedProp,\n\tsize: sizeProp,\n\tonSelect,\n\t...props\n}: CarouselIndicatorProps) {\n\t// Read context loosely so the indicator can also stand alone (previews,\n\t// docs) by passing `count`/`selected` directly.\n\tconst ctx = React.useContext(CarouselContext);\n\tconst inheritedSize = useComponentSize(sizeProp);\n\n\tconst size = sizeProp ?? ctx?.size ?? inheritedSize;\n\tconst count = countProp ?? ctx?.scrollSnaps.length ?? 0;\n\tconst selected = selectedProp ?? ctx?.selectedIndex ?? 0;\n\tconst handleSelect = onSelect ?? ctx?.scrollTo;\n\n\tif (count <= 1) return null;\n\n\tconst { dot, gap, padX, padY } = INDICATOR_SIZES[size];\n\tconst { dots, start, visible } = getIndicatorWindow(count, selected);\n\tconst step = dot + gap;\n\t// Viewport width clips the strip to exactly `visible` dots; the strip then\n\t// translates by `start` steps so the windowed dots line up under it.\n\tconst viewportWidth = visible * step - gap;\n\n\treturn (\n\t\t<div\n\t\t\tdata-slot=\"carousel-indicator\"\n\t\t\tclassName={cn(\n\t\t\t\t\"absolute left-1/2 z-10 flex -translate-x-1/2 items-center justify-center rounded-full\",\n\t\t\t\tINDICATOR_OFFSET[size],\n\t\t\t\tglassChrome,\n\t\t\t\tclassName,\n\t\t\t)}\n\t\t\tstyle={{ padding: `${padY}px ${padX}px` }}\n\t\t\trole=\"tablist\"\n\t\t\taria-label=\"Slide indicators\"\n\t\t\t{...props}\n\t\t>\n\t\t\t<div className=\"overflow-hidden\" style={{ width: viewportWidth }}>\n\t\t\t\t<div\n\t\t\t\t\tclassName=\"flex items-center transition-transform duration-300 ease-out\"\n\t\t\t\t\tstyle={{ gap, transform: `translateX(${-start * step}px)` }}\n\t\t\t\t>\n\t\t\t\t\t{dots.map(({ index, faded }) => {\n\t\t\t\t\t\tconst isActive = index === selected;\n\t\t\t\t\t\treturn (\n\t\t\t\t\t\t\t<button\n\t\t\t\t\t\t\t\tkey={index}\n\t\t\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\t\t\trole=\"tab\"\n\t\t\t\t\t\t\t\taria-selected={isActive}\n\t\t\t\t\t\t\t\taria-label={`Go to slide ${index + 1}`}\n\t\t\t\t\t\t\t\tonClick={() => handleSelect?.(index)}\n\t\t\t\t\t\t\t\tclassName={cn(\n\t\t\t\t\t\t\t\t\t\"shrink-0 rounded-full bg-fg-on-brand transition-[transform,opacity] duration-300 ease-out\",\n\t\t\t\t\t\t\t\t\thandleSelect ? \"cursor-pointer\" : \"cursor-default\",\n\t\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t\tstyle={{\n\t\t\t\t\t\t\t\t\twidth: dot,\n\t\t\t\t\t\t\t\t\theight: dot,\n\t\t\t\t\t\t\t\t\topacity: faded ? 0.3 : isActive ? 1 : 0.55,\n\t\t\t\t\t\t\t\t\ttransform: faded ? \"scale(0.6)\" : \"scale(1)\",\n\t\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t);\n\t\t\t\t\t})}\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</div>\n\t);\n}\n\nexport {\n\ttype CarouselApi,\n\tCarousel,\n\tCarouselContent,\n\tCarouselItem,\n\tCarouselPrevious,\n\tCarouselNext,\n\tCarouselIndicator,\n\tuseCarousel,\n};\n"],"mappings":"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","names":["ChevronLeftIcon","ChevronRightIcon","React","jsx","cva","jsx","jsxs","cva","api","Fragment","jsx","jsxs","ChevronLeftIcon","ChevronRightIcon"]}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { ComponentSize } from './size-context.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Pie / donut charts are composed from recharts plot primitives directly —
|
|
6
|
+
* recharts identifies `PieChart`, `Pie`, and `Cell` by component identity, so
|
|
7
|
+
* they cannot be wrapped. This module supplies the giddaa-specific pieces that
|
|
8
|
+
* would otherwise be copy-pasted: per-size layout numbers, a categorical
|
|
9
|
+
* palette, the donut center label, and the rich legend (dot · name · value ·
|
|
10
|
+
* percentage). Pair them with the shared frame from `./chart`.
|
|
11
|
+
*
|
|
12
|
+
* A donut keeps an open center for a total label (`innerRadius` > 0); a pie
|
|
13
|
+
* fills the circle (`innerRadius={0}`). Both read the same layout.
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* const chart = usePieChartLayout({ size: "md", type: "donut" });
|
|
17
|
+
* <ChartCard size="md">
|
|
18
|
+
* <div className="flex items-center gap-4">
|
|
19
|
+
* <ChartContainer config={config} {...chart.container}>
|
|
20
|
+
* <PieChart>
|
|
21
|
+
* <Pie data={data} dataKey="value" nameKey="label" {...chart.pie}>
|
|
22
|
+
* {data.map((d) => <Cell key={d.label} fill={d.color} />)}
|
|
23
|
+
* <Label content={<PieCenterLabel total="₦1.1M" caption="Total spend" />} />
|
|
24
|
+
* </Pie>
|
|
25
|
+
* </PieChart>
|
|
26
|
+
* </ChartContainer>
|
|
27
|
+
* <PieChartLegend items={legendItems} />
|
|
28
|
+
* </div>
|
|
29
|
+
* </ChartCard>
|
|
30
|
+
*/
|
|
31
|
+
/** Pie sizes line up with the three shared `ComponentSize` values. */
|
|
32
|
+
type PieChartSize = ComponentSize;
|
|
33
|
+
type PieChartLayout = {
|
|
34
|
+
/** Square plot box in px (width = height); width is NOT parent-driven here. */
|
|
35
|
+
size: number;
|
|
36
|
+
/** Donut hole radius in px. Pass `0` to the `Pie` for a filled pie. */
|
|
37
|
+
innerRadius: number;
|
|
38
|
+
/** Outer ring radius in px. */
|
|
39
|
+
outerRadius: number;
|
|
40
|
+
/** Gap between segments, in degrees. */
|
|
41
|
+
paddingAngle: number;
|
|
42
|
+
/** Corner radius of each segment. */
|
|
43
|
+
cornerRadius: number;
|
|
44
|
+
/** Center total font size. */
|
|
45
|
+
totalFontSize: number;
|
|
46
|
+
/** Center caption font size. */
|
|
47
|
+
captionFontSize: number;
|
|
48
|
+
};
|
|
49
|
+
declare const PIE_CHART_LAYOUT: Record<PieChartSize, PieChartLayout>;
|
|
50
|
+
/**
|
|
51
|
+
* Categorical palette for pie/donut segments, ordered by share. Giddaa has no
|
|
52
|
+
* `chart-1`…`chart-N` tokens yet, so these map to the brand scales: forest →
|
|
53
|
+
* brass → moss → clay → stone. Assign by index, or override per `Cell`.
|
|
54
|
+
*/
|
|
55
|
+
declare const PIE_CHART_PALETTE: readonly ["var(--color-forest-700)", "var(--color-brass-500)", "var(--color-moss-600)", "var(--color-clay-500)", "var(--color-stone-500)"];
|
|
56
|
+
/**
|
|
57
|
+
* Resolve the pie-chart layout for an explicit or inherited component size and
|
|
58
|
+
* chart `type`. The returned object groups the giddaa defaults by the recharts
|
|
59
|
+
* component they belong to — spread `container` onto `ChartContainer` and `pie`
|
|
60
|
+
* onto `Pie`, then pass the data-specific props (`data`, `dataKey`, `nameKey`)
|
|
61
|
+
* directly afterwards. `type="pie"` collapses the donut hole (`innerRadius` 0).
|
|
62
|
+
*/
|
|
63
|
+
declare function usePieChartLayout({ size, type, }?: {
|
|
64
|
+
size?: PieChartSize;
|
|
65
|
+
type?: "donut" | "pie";
|
|
66
|
+
}): {
|
|
67
|
+
container: {
|
|
68
|
+
className: string;
|
|
69
|
+
style: {
|
|
70
|
+
width: number;
|
|
71
|
+
height: number;
|
|
72
|
+
};
|
|
73
|
+
};
|
|
74
|
+
pie: {
|
|
75
|
+
innerRadius: number;
|
|
76
|
+
outerRadius: number;
|
|
77
|
+
paddingAngle: number;
|
|
78
|
+
cornerRadius: number;
|
|
79
|
+
strokeWidth: number;
|
|
80
|
+
};
|
|
81
|
+
label: {
|
|
82
|
+
totalFontSize: number;
|
|
83
|
+
captionFontSize: number;
|
|
84
|
+
};
|
|
85
|
+
};
|
|
86
|
+
/**
|
|
87
|
+
* Center label for a donut — the hero total over a short caption. Pass it to
|
|
88
|
+
* recharts as `<Label content={<PieCenterLabel total=… caption=… />} />` inside
|
|
89
|
+
* the `Pie`; recharts injects the `viewBox` carrying the polar center (cx/cy).
|
|
90
|
+
*/
|
|
91
|
+
type PieCenterLabelProps = {
|
|
92
|
+
/** Hero figure — pre-formatted, e.g. "₦1.1M". */
|
|
93
|
+
total: React.ReactNode;
|
|
94
|
+
/** Short label describing what the total represents, e.g. "Total spend". */
|
|
95
|
+
caption?: React.ReactNode;
|
|
96
|
+
/** Explicit size; otherwise inherited from the nearest `SizeProvider`. */
|
|
97
|
+
size?: PieChartSize;
|
|
98
|
+
viewBox?: {
|
|
99
|
+
cx?: number;
|
|
100
|
+
cy?: number;
|
|
101
|
+
};
|
|
102
|
+
};
|
|
103
|
+
declare function PieCenterLabel({ total, caption, size, viewBox, }: PieCenterLabelProps): React.JSX.Element | null;
|
|
104
|
+
/**
|
|
105
|
+
* Rich legend for pie/donut charts. Unlike the shared `ChartLegendContent`
|
|
106
|
+
* (marker + label only), each row pairs a colour dot with the category name, its
|
|
107
|
+
* value, and its share — laid out as aligned columns so the figures read like a
|
|
108
|
+
* mini table. Values and percentages are pre-formatted by the caller.
|
|
109
|
+
*/
|
|
110
|
+
type PieLegendItem = {
|
|
111
|
+
/** Category name, e.g. "Housing". */
|
|
112
|
+
label: string;
|
|
113
|
+
/** Segment colour — should match the matching `Cell` fill. */
|
|
114
|
+
color: string;
|
|
115
|
+
/** Pre-formatted value, e.g. "₦420k". */
|
|
116
|
+
value: React.ReactNode;
|
|
117
|
+
/** Pre-formatted share, e.g. "38%". */
|
|
118
|
+
percentage: React.ReactNode;
|
|
119
|
+
};
|
|
120
|
+
declare function PieChartLegend({ items, size, className, ...props }: React.ComponentProps<"ul"> & {
|
|
121
|
+
items: PieLegendItem[];
|
|
122
|
+
size?: PieChartSize;
|
|
123
|
+
}): React.JSX.Element;
|
|
124
|
+
|
|
125
|
+
export { PIE_CHART_LAYOUT, PIE_CHART_PALETTE, PieCenterLabel, type PieCenterLabelProps, type PieChartLayout, PieChartLegend, type PieChartSize, type PieLegendItem, usePieChartLayout };
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
// src/components/ui/pie-chart.tsx
|
|
2
|
+
import { cva } from "class-variance-authority";
|
|
3
|
+
|
|
4
|
+
// src/lib/cn.ts
|
|
5
|
+
import { clsx } from "clsx";
|
|
6
|
+
import { extendTailwindMerge } from "tailwind-merge";
|
|
7
|
+
var twMerge = extendTailwindMerge({
|
|
8
|
+
extend: {
|
|
9
|
+
classGroups: {
|
|
10
|
+
"font-size": [
|
|
11
|
+
{
|
|
12
|
+
text: [
|
|
13
|
+
"gdt-display",
|
|
14
|
+
"gdt-h1",
|
|
15
|
+
"gdt-h2",
|
|
16
|
+
"gdt-h3",
|
|
17
|
+
"gdt-h4",
|
|
18
|
+
"gdt-h5",
|
|
19
|
+
"gdt-xl",
|
|
20
|
+
"gdt-lg",
|
|
21
|
+
"gdt-md",
|
|
22
|
+
"gdt-sm",
|
|
23
|
+
"gdt-xs",
|
|
24
|
+
"gdt-subtext",
|
|
25
|
+
"gdt-capitalized"
|
|
26
|
+
]
|
|
27
|
+
}
|
|
28
|
+
]
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
function cn(...inputs) {
|
|
33
|
+
return twMerge(clsx(inputs));
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// src/components/ui/size-context.tsx
|
|
37
|
+
import { createContext, useContext } from "react";
|
|
38
|
+
import { Fragment, jsx } from "react/jsx-runtime";
|
|
39
|
+
var SizeContext = createContext(void 0);
|
|
40
|
+
function useComponentSize(size, fallback = "md") {
|
|
41
|
+
const inheritedSize = useContext(SizeContext);
|
|
42
|
+
return size ?? inheritedSize ?? fallback;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// src/components/ui/pie-chart.tsx
|
|
46
|
+
import { jsx as jsx2, jsxs } from "react/jsx-runtime";
|
|
47
|
+
var PIE_CHART_LAYOUT = {
|
|
48
|
+
sm: {
|
|
49
|
+
size: 132,
|
|
50
|
+
innerRadius: 42,
|
|
51
|
+
outerRadius: 64,
|
|
52
|
+
paddingAngle: 2,
|
|
53
|
+
cornerRadius: 0,
|
|
54
|
+
totalFontSize: 18,
|
|
55
|
+
captionFontSize: 9
|
|
56
|
+
},
|
|
57
|
+
md: {
|
|
58
|
+
size: 176,
|
|
59
|
+
innerRadius: 56,
|
|
60
|
+
outerRadius: 84,
|
|
61
|
+
paddingAngle: 2,
|
|
62
|
+
cornerRadius: 0,
|
|
63
|
+
totalFontSize: 24,
|
|
64
|
+
captionFontSize: 11
|
|
65
|
+
},
|
|
66
|
+
lg: {
|
|
67
|
+
size: 224,
|
|
68
|
+
innerRadius: 72,
|
|
69
|
+
outerRadius: 108,
|
|
70
|
+
paddingAngle: 2,
|
|
71
|
+
cornerRadius: 0,
|
|
72
|
+
totalFontSize: 30,
|
|
73
|
+
captionFontSize: 12
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
var PIE_CHART_PALETTE = [
|
|
77
|
+
"var(--color-forest-700)",
|
|
78
|
+
"var(--color-brass-500)",
|
|
79
|
+
"var(--color-moss-600)",
|
|
80
|
+
"var(--color-clay-500)",
|
|
81
|
+
"var(--color-stone-500)"
|
|
82
|
+
];
|
|
83
|
+
function usePieChartLayout({
|
|
84
|
+
size,
|
|
85
|
+
type = "donut"
|
|
86
|
+
} = {}) {
|
|
87
|
+
const layout = PIE_CHART_LAYOUT[useComponentSize(size)];
|
|
88
|
+
return {
|
|
89
|
+
container: {
|
|
90
|
+
className: "aspect-square shrink-0",
|
|
91
|
+
style: { width: layout.size, height: layout.size }
|
|
92
|
+
},
|
|
93
|
+
pie: {
|
|
94
|
+
innerRadius: type === "pie" ? 0 : layout.innerRadius,
|
|
95
|
+
outerRadius: layout.outerRadius,
|
|
96
|
+
paddingAngle: type === "pie" ? 0 : layout.paddingAngle,
|
|
97
|
+
cornerRadius: layout.cornerRadius,
|
|
98
|
+
strokeWidth: 0
|
|
99
|
+
},
|
|
100
|
+
label: {
|
|
101
|
+
totalFontSize: layout.totalFontSize,
|
|
102
|
+
captionFontSize: layout.captionFontSize
|
|
103
|
+
}
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
function PieCenterLabel({
|
|
107
|
+
total,
|
|
108
|
+
caption,
|
|
109
|
+
size,
|
|
110
|
+
viewBox
|
|
111
|
+
}) {
|
|
112
|
+
const { label } = usePieChartLayout({ size });
|
|
113
|
+
if (!viewBox || viewBox.cx == null || viewBox.cy == null) {
|
|
114
|
+
return null;
|
|
115
|
+
}
|
|
116
|
+
const { cx, cy } = viewBox;
|
|
117
|
+
const captionY = cy + label.totalFontSize * 0.72;
|
|
118
|
+
return /* @__PURE__ */ jsxs("text", { x: cx, y: cy, textAnchor: "middle", dominantBaseline: "central", children: [
|
|
119
|
+
/* @__PURE__ */ jsx2(
|
|
120
|
+
"tspan",
|
|
121
|
+
{
|
|
122
|
+
x: cx,
|
|
123
|
+
y: caption == null ? cy : cy - label.captionFontSize * 0.5,
|
|
124
|
+
className: "fill-fg-primary font-extrabold tabular-nums",
|
|
125
|
+
style: { fontSize: label.totalFontSize },
|
|
126
|
+
children: total
|
|
127
|
+
}
|
|
128
|
+
),
|
|
129
|
+
caption != null && /* @__PURE__ */ jsx2(
|
|
130
|
+
"tspan",
|
|
131
|
+
{
|
|
132
|
+
x: cx,
|
|
133
|
+
y: captionY,
|
|
134
|
+
className: "fill-fg-secondary font-medium",
|
|
135
|
+
style: { fontSize: label.captionFontSize },
|
|
136
|
+
children: caption
|
|
137
|
+
}
|
|
138
|
+
)
|
|
139
|
+
] });
|
|
140
|
+
}
|
|
141
|
+
var pieLegendVariants = cva("flex min-w-0 flex-col", {
|
|
142
|
+
variants: {
|
|
143
|
+
size: {
|
|
144
|
+
sm: "gap-1.5 text-gdt-xs",
|
|
145
|
+
md: "gap-2 text-gdt-sm",
|
|
146
|
+
lg: "gap-2.5 text-gdt-sm"
|
|
147
|
+
}
|
|
148
|
+
},
|
|
149
|
+
defaultVariants: { size: "md" }
|
|
150
|
+
});
|
|
151
|
+
var pieLegendDotVariants = cva("shrink-0 rounded-full", {
|
|
152
|
+
variants: {
|
|
153
|
+
size: { sm: "size-2", md: "size-2.5", lg: "size-3" }
|
|
154
|
+
},
|
|
155
|
+
defaultVariants: { size: "md" }
|
|
156
|
+
});
|
|
157
|
+
function PieChartLegend({
|
|
158
|
+
items,
|
|
159
|
+
size,
|
|
160
|
+
className,
|
|
161
|
+
...props
|
|
162
|
+
}) {
|
|
163
|
+
const resolvedSize = useComponentSize(size);
|
|
164
|
+
return /* @__PURE__ */ jsx2(
|
|
165
|
+
"ul",
|
|
166
|
+
{
|
|
167
|
+
className: cn(pieLegendVariants({ size: resolvedSize }), className),
|
|
168
|
+
...props,
|
|
169
|
+
children: items.map((item) => /* @__PURE__ */ jsxs("li", { className: "flex items-center gap-2.5", children: [
|
|
170
|
+
/* @__PURE__ */ jsx2(
|
|
171
|
+
"span",
|
|
172
|
+
{
|
|
173
|
+
"aria-hidden": "true",
|
|
174
|
+
className: cn(pieLegendDotVariants({ size: resolvedSize })),
|
|
175
|
+
style: { backgroundColor: item.color }
|
|
176
|
+
}
|
|
177
|
+
),
|
|
178
|
+
/* @__PURE__ */ jsx2("span", { className: "min-w-0 flex-1 truncate text-fg-primary", children: item.label }),
|
|
179
|
+
/* @__PURE__ */ jsx2("span", { className: "shrink-0 tabular-nums text-fg-secondary", children: item.value }),
|
|
180
|
+
/* @__PURE__ */ jsx2("span", { className: "w-9 shrink-0 text-right font-bold tabular-nums text-fg-primary", children: item.percentage })
|
|
181
|
+
] }, item.label))
|
|
182
|
+
}
|
|
183
|
+
);
|
|
184
|
+
}
|
|
185
|
+
export {
|
|
186
|
+
PIE_CHART_LAYOUT,
|
|
187
|
+
PIE_CHART_PALETTE,
|
|
188
|
+
PieCenterLabel,
|
|
189
|
+
PieChartLegend,
|
|
190
|
+
usePieChartLayout
|
|
191
|
+
};
|
|
192
|
+
//# sourceMappingURL=pie-chart.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/components/ui/pie-chart.tsx","../src/lib/cn.ts","../src/components/ui/size-context.tsx"],"sourcesContent":["\"use client\";\n\nimport { cva } from \"class-variance-authority\";\nimport type * as React from \"react\";\nimport { cn } from \"../../lib/cn\";\nimport { type ComponentSize, useComponentSize } from \"./size-context\";\n\n/**\n * Pie / donut charts are composed from recharts plot primitives directly —\n * recharts identifies `PieChart`, `Pie`, and `Cell` by component identity, so\n * they cannot be wrapped. This module supplies the giddaa-specific pieces that\n * would otherwise be copy-pasted: per-size layout numbers, a categorical\n * palette, the donut center label, and the rich legend (dot · name · value ·\n * percentage). Pair them with the shared frame from `./chart`.\n *\n * A donut keeps an open center for a total label (`innerRadius` > 0); a pie\n * fills the circle (`innerRadius={0}`). Both read the same layout.\n *\n * @example\n * const chart = usePieChartLayout({ size: \"md\", type: \"donut\" });\n * <ChartCard size=\"md\">\n * <div className=\"flex items-center gap-4\">\n * <ChartContainer config={config} {...chart.container}>\n * <PieChart>\n * <Pie data={data} dataKey=\"value\" nameKey=\"label\" {...chart.pie}>\n * {data.map((d) => <Cell key={d.label} fill={d.color} />)}\n * <Label content={<PieCenterLabel total=\"₦1.1M\" caption=\"Total spend\" />} />\n * </Pie>\n * </PieChart>\n * </ChartContainer>\n * <PieChartLegend items={legendItems} />\n * </div>\n * </ChartCard>\n */\n\n/** Pie sizes line up with the three shared `ComponentSize` values. */\ntype PieChartSize = ComponentSize;\n\ntype PieChartLayout = {\n\t/** Square plot box in px (width = height); width is NOT parent-driven here. */\n\tsize: number;\n\t/** Donut hole radius in px. Pass `0` to the `Pie` for a filled pie. */\n\tinnerRadius: number;\n\t/** Outer ring radius in px. */\n\touterRadius: number;\n\t/** Gap between segments, in degrees. */\n\tpaddingAngle: number;\n\t/** Corner radius of each segment. */\n\tcornerRadius: number;\n\t/** Center total font size. */\n\ttotalFontSize: number;\n\t/** Center caption font size. */\n\tcaptionFontSize: number;\n};\n\nconst PIE_CHART_LAYOUT: Record<PieChartSize, PieChartLayout> = {\n\tsm: {\n\t\tsize: 132,\n\t\tinnerRadius: 42,\n\t\touterRadius: 64,\n\t\tpaddingAngle: 2,\n\t\tcornerRadius: 0,\n\t\ttotalFontSize: 18,\n\t\tcaptionFontSize: 9,\n\t},\n\tmd: {\n\t\tsize: 176,\n\t\tinnerRadius: 56,\n\t\touterRadius: 84,\n\t\tpaddingAngle: 2,\n\t\tcornerRadius: 0,\n\t\ttotalFontSize: 24,\n\t\tcaptionFontSize: 11,\n\t},\n\tlg: {\n\t\tsize: 224,\n\t\tinnerRadius: 72,\n\t\touterRadius: 108,\n\t\tpaddingAngle: 2,\n\t\tcornerRadius: 0,\n\t\ttotalFontSize: 30,\n\t\tcaptionFontSize: 12,\n\t},\n};\n\n/**\n * Categorical palette for pie/donut segments, ordered by share. Giddaa has no\n * `chart-1`…`chart-N` tokens yet, so these map to the brand scales: forest →\n * brass → moss → clay → stone. Assign by index, or override per `Cell`.\n */\nconst PIE_CHART_PALETTE = [\n\t\"var(--color-forest-700)\",\n\t\"var(--color-brass-500)\",\n\t\"var(--color-moss-600)\",\n\t\"var(--color-clay-500)\",\n\t\"var(--color-stone-500)\",\n] as const;\n\n/**\n * Resolve the pie-chart layout for an explicit or inherited component size and\n * chart `type`. The returned object groups the giddaa defaults by the recharts\n * component they belong to — spread `container` onto `ChartContainer` and `pie`\n * onto `Pie`, then pass the data-specific props (`data`, `dataKey`, `nameKey`)\n * directly afterwards. `type=\"pie\"` collapses the donut hole (`innerRadius` 0).\n */\nfunction usePieChartLayout({\n\tsize,\n\ttype = \"donut\",\n}: {\n\tsize?: PieChartSize;\n\ttype?: \"donut\" | \"pie\";\n} = {}) {\n\tconst layout = PIE_CHART_LAYOUT[useComponentSize(size)];\n\n\treturn {\n\t\tcontainer: {\n\t\t\tclassName: \"aspect-square shrink-0\",\n\t\t\tstyle: { width: layout.size, height: layout.size },\n\t\t},\n\t\tpie: {\n\t\t\tinnerRadius: type === \"pie\" ? 0 : layout.innerRadius,\n\t\t\touterRadius: layout.outerRadius,\n\t\t\tpaddingAngle: type === \"pie\" ? 0 : layout.paddingAngle,\n\t\t\tcornerRadius: layout.cornerRadius,\n\t\t\tstrokeWidth: 0,\n\t\t},\n\t\tlabel: {\n\t\t\ttotalFontSize: layout.totalFontSize,\n\t\t\tcaptionFontSize: layout.captionFontSize,\n\t\t},\n\t};\n}\n\n/**\n * Center label for a donut — the hero total over a short caption. Pass it to\n * recharts as `<Label content={<PieCenterLabel total=… caption=… />} />` inside\n * the `Pie`; recharts injects the `viewBox` carrying the polar center (cx/cy).\n */\ntype PieCenterLabelProps = {\n\t/** Hero figure — pre-formatted, e.g. \"₦1.1M\". */\n\ttotal: React.ReactNode;\n\t/** Short label describing what the total represents, e.g. \"Total spend\". */\n\tcaption?: React.ReactNode;\n\t/** Explicit size; otherwise inherited from the nearest `SizeProvider`. */\n\tsize?: PieChartSize;\n\t// Injected by recharts at render time.\n\tviewBox?: { cx?: number; cy?: number };\n};\n\nfunction PieCenterLabel({\n\ttotal,\n\tcaption,\n\tsize,\n\tviewBox,\n}: PieCenterLabelProps) {\n\tconst { label } = usePieChartLayout({ size });\n\n\tif (!viewBox || viewBox.cx == null || viewBox.cy == null) {\n\t\treturn null;\n\t}\n\n\tconst { cx, cy } = viewBox;\n\tconst captionY = cy + label.totalFontSize * 0.72;\n\n\treturn (\n\t\t<text x={cx} y={cy} textAnchor=\"middle\" dominantBaseline=\"central\">\n\t\t\t<tspan\n\t\t\t\tx={cx}\n\t\t\t\ty={caption == null ? cy : cy - label.captionFontSize * 0.5}\n\t\t\t\tclassName=\"fill-fg-primary font-extrabold tabular-nums\"\n\t\t\t\tstyle={{ fontSize: label.totalFontSize }}\n\t\t\t>\n\t\t\t\t{total}\n\t\t\t</tspan>\n\t\t\t{caption != null && (\n\t\t\t\t<tspan\n\t\t\t\t\tx={cx}\n\t\t\t\t\ty={captionY}\n\t\t\t\t\tclassName=\"fill-fg-secondary font-medium\"\n\t\t\t\t\tstyle={{ fontSize: label.captionFontSize }}\n\t\t\t\t>\n\t\t\t\t\t{caption}\n\t\t\t\t</tspan>\n\t\t\t)}\n\t\t</text>\n\t);\n}\n\n/**\n * Rich legend for pie/donut charts. Unlike the shared `ChartLegendContent`\n * (marker + label only), each row pairs a colour dot with the category name, its\n * value, and its share — laid out as aligned columns so the figures read like a\n * mini table. Values and percentages are pre-formatted by the caller.\n */\ntype PieLegendItem = {\n\t/** Category name, e.g. \"Housing\". */\n\tlabel: string;\n\t/** Segment colour — should match the matching `Cell` fill. */\n\tcolor: string;\n\t/** Pre-formatted value, e.g. \"₦420k\". */\n\tvalue: React.ReactNode;\n\t/** Pre-formatted share, e.g. \"38%\". */\n\tpercentage: React.ReactNode;\n};\n\nconst pieLegendVariants = cva(\"flex min-w-0 flex-col\", {\n\tvariants: {\n\t\tsize: {\n\t\t\tsm: \"gap-1.5 text-gdt-xs\",\n\t\t\tmd: \"gap-2 text-gdt-sm\",\n\t\t\tlg: \"gap-2.5 text-gdt-sm\",\n\t\t},\n\t},\n\tdefaultVariants: { size: \"md\" },\n});\n\nconst pieLegendDotVariants = cva(\"shrink-0 rounded-full\", {\n\tvariants: {\n\t\tsize: { sm: \"size-2\", md: \"size-2.5\", lg: \"size-3\" },\n\t},\n\tdefaultVariants: { size: \"md\" },\n});\n\nfunction PieChartLegend({\n\titems,\n\tsize,\n\tclassName,\n\t...props\n}: React.ComponentProps<\"ul\"> & {\n\titems: PieLegendItem[];\n\tsize?: PieChartSize;\n}) {\n\tconst resolvedSize = useComponentSize(size);\n\n\treturn (\n\t\t<ul\n\t\t\tclassName={cn(pieLegendVariants({ size: resolvedSize }), className)}\n\t\t\t{...props}\n\t\t>\n\t\t\t{items.map((item) => (\n\t\t\t\t<li key={item.label} className=\"flex items-center gap-2.5\">\n\t\t\t\t\t<span\n\t\t\t\t\t\taria-hidden=\"true\"\n\t\t\t\t\t\tclassName={cn(pieLegendDotVariants({ size: resolvedSize }))}\n\t\t\t\t\t\tstyle={{ backgroundColor: item.color }}\n\t\t\t\t\t/>\n\t\t\t\t\t<span className=\"min-w-0 flex-1 truncate text-fg-primary\">\n\t\t\t\t\t\t{item.label}\n\t\t\t\t\t</span>\n\t\t\t\t\t<span className=\"shrink-0 tabular-nums text-fg-secondary\">\n\t\t\t\t\t\t{item.value}\n\t\t\t\t\t</span>\n\t\t\t\t\t<span className=\"w-9 shrink-0 text-right font-bold tabular-nums text-fg-primary\">\n\t\t\t\t\t\t{item.percentage}\n\t\t\t\t\t</span>\n\t\t\t\t</li>\n\t\t\t))}\n\t\t</ul>\n\t);\n}\n\nexport {\n\tPIE_CHART_LAYOUT,\n\tPIE_CHART_PALETTE,\n\tPieCenterLabel,\n\tPieChartLegend,\n\tusePieChartLayout,\n};\nexport type {\n\tPieChartLayout,\n\tPieChartSize,\n\tPieCenterLabelProps,\n\tPieLegendItem,\n};\n","import { type ClassValue, clsx } from \"clsx\";\nimport { extendTailwindMerge } from \"tailwind-merge\";\n\nconst twMerge = extendTailwindMerge({\n\textend: {\n\t\tclassGroups: {\n\t\t\t\"font-size\": [\n\t\t\t\t{\n\t\t\t\t\ttext: [\n\t\t\t\t\t\t\"gdt-display\",\n\t\t\t\t\t\t\"gdt-h1\",\n\t\t\t\t\t\t\"gdt-h2\",\n\t\t\t\t\t\t\"gdt-h3\",\n\t\t\t\t\t\t\"gdt-h4\",\n\t\t\t\t\t\t\"gdt-h5\",\n\t\t\t\t\t\t\"gdt-xl\",\n\t\t\t\t\t\t\"gdt-lg\",\n\t\t\t\t\t\t\"gdt-md\",\n\t\t\t\t\t\t\"gdt-sm\",\n\t\t\t\t\t\t\"gdt-xs\",\n\t\t\t\t\t\t\"gdt-subtext\",\n\t\t\t\t\t\t\"gdt-capitalized\",\n\t\t\t\t\t],\n\t\t\t\t},\n\t\t\t],\n\t\t},\n\t},\n});\n\nexport function cn(...inputs: ClassValue[]) {\n\treturn twMerge(clsx(inputs));\n}\n","\"use client\";\n\nimport { createContext, type ReactNode, useContext } from \"react\";\n\ntype ComponentSize = \"sm\" | \"md\" | \"lg\";\n\nconst SizeContext = createContext<ComponentSize | undefined>(undefined);\n\nfunction SizeProvider({\n\tchildren,\n\tsize,\n}: {\n\tchildren: ReactNode;\n\tsize?: ComponentSize | null;\n}) {\n\tif (!size) {\n\t\treturn <>{children}</>;\n\t}\n\n\treturn <SizeContext.Provider value={size}>{children}</SizeContext.Provider>;\n}\n\nfunction useComponentSize(\n\tsize?: ComponentSize | null,\n\tfallback: ComponentSize = \"md\",\n) {\n\tconst inheritedSize = useContext(SizeContext);\n\treturn size ?? inheritedSize ?? fallback;\n}\n\nexport { SizeProvider, useComponentSize, type ComponentSize };\n"],"mappings":"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","names":["jsx"]}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { Popover as Popover$1 } from '@base-ui/react/popover';
|
|
2
|
+
import * as React from 'react';
|
|
3
|
+
import { ComponentSize } from './size-context.js';
|
|
4
|
+
|
|
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" | "side" | "sideOffset"> & {
|
|
8
|
+
showArrow?: boolean;
|
|
9
|
+
size?: ComponentSize;
|
|
10
|
+
};
|
|
11
|
+
declare function PopoverContent({ className, align, alignOffset, 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
|
+
|
|
20
|
+
export { Popover, PopoverBody, PopoverContent, PopoverDescription, PopoverFooter, PopoverHeader, PopoverTitle, PopoverTrigger };
|