@clicktap/ui 0.40.8 → 0.40.10

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 (40) hide show
  1. package/components/Accordion/Accordion.d.ts +1 -1
  2. package/components/Accordion/Accordion.js +1 -1
  3. package/components/Accordion/index.d.ts +2 -2
  4. package/components/Accordion/index.js +1 -1
  5. package/components/Badge/Badge.js +1 -1
  6. package/components/Badge/Badge.types.d.ts +1 -1
  7. package/components/Badge/index.js +1 -1
  8. package/components/Collection/CollectionFilters/CollectionFilters.js +1 -1
  9. package/components/Collection/CollectionFilters/index.js +1 -1
  10. package/components/Collection/index.js +1 -1
  11. package/components/Pagination/Pagination.d.ts +1 -1
  12. package/components/Pagination/Pagination.js +1 -1
  13. package/components/Pagination/index.js +1 -1
  14. package/components/Tooltip/Tooltip.d.ts +1 -1
  15. package/components/Tooltip/Tooltip.js +1 -1
  16. package/components/Tooltip/Tooltip.types.d.ts +1 -1
  17. package/components/Tooltip/index.js +1 -1
  18. package/docs/migrations/2026/01/2026-01-14-external-link-detection.md +290 -0
  19. package/docs/migrations/2026/01/2026-01-14-use-route-hook.md +142 -0
  20. package/docs/migrations/2026/01/2026-01-15-input-disable-skeleton.md +82 -0
  21. package/docs/migrations/2026/02/2026-02-16-env-utils-from-clicktap-ui.md +94 -0
  22. package/docs/migrations/2026/02/2026-02-17-select-popover-portal-container-restored.md +84 -0
  23. package/docs/migrations/2026/03/2026-03-24-image-dimmed-hidden-onready.md +108 -0
  24. package/docs/migrations/2026/03/2026-03-24-modal-overlay-backdrop-dismiss.md +81 -0
  25. package/docs/migrations/2026/04/2026-04-17-select-address-input-onchange-key-or-item.md +182 -0
  26. package/docs/migrations/2026/04/2026-04-24-image-placeholder-frontend-fallback.md +85 -0
  27. package/docs/migrations/2026/04/2026-04-30-prune-invalid-pdp-selections.md +166 -0
  28. package/docs/migrations/2026/04/2026-04-30-top-down-pdp-option-dependency.md +286 -0
  29. package/docs/migrations/2026/05/2026-05-01-build-pdp-url-util.md +141 -0
  30. package/docs/migrations/2026/05/2026-05-03-auth-aware-graphql-client.md +278 -0
  31. package/docs/migrations/2026/05/2026-05-20-framer-motion-11-idioms.md +391 -0
  32. package/docs/migrations/2026/05/2026-05-21-button-loading-content.md +265 -0
  33. package/docs/migrations/2026/05/2026-05-21-password-check-hide-on-empty.md +50 -0
  34. package/docs/migrations/2026/05/2026-05-29-password-check-requirements-variant.md +63 -0
  35. package/docs/migrations/2026/06/2026-06-17-select-standardization.md +286 -0
  36. package/docs/migrations/2026/06/2026-06-18-select-hiddenselect-setvalue-crash.md +75 -0
  37. package/docs/migrations/2026/07/2026-07-31-ui-exports-bundler-resolution.md +50 -0
  38. package/docs/migrations/2026/08/2026-08-02-nextui-to-heroui.md +65 -0
  39. package/docs/migrations/README.md +31 -0
  40. package/package.json +7 -5
@@ -1,4 +1,4 @@
1
- import { AccordionProps } from '@nextui-org/accordion';
1
+ import { AccordionProps } from '@heroui/accordion';
2
2
  export declare function Accordion({ children, variant, isCompact, className, itemClasses, ...props }: AccordionProps): import("react/jsx-runtime").JSX.Element;
3
3
  export default Accordion;
4
4
  export declare function AccordionItemArrow(): import("react/jsx-runtime").JSX.Element;
@@ -1 +1 @@
1
- "use client";import{jsx as d}from"react/jsx-runtime";import{Accordion as a}from"@nextui-org/accordion";import{cn as r}from"../../utils/cn.js";import{DownArrow as u}from"../Icon/DownArrow.js";function x({children:n,variant:t,isCompact:o,className:l,itemClasses:e,...i}){return d(a,{variant:t,isCompact:o,dividerProps:{className:r("my-2 border-solid border-slate-200",t==="shadow"&&"border-slate-300")},className:r("px-0",t==="bordered"&&["px-4 py-4 rounded-xl border-2 border-slate-200"],t==="shadow"&&["px-4 py-4 rounded-xl bg-slate-100 shadow-slate-200 border border-slate-200"],l),itemClasses:{base:r("w-full py-2",'data-[disabled="true"]:pointer-events-none data-[disabled="true"]:opacity-50',t==="bordered"&&["rounded-xl py-0"],t==="splitted"&&["flex flex-col bg-slate-100 rounded-xl border border-slate-200"],o&&"py-0",e?.base),trigger:r("w-full h-full flex items-center gap-3 py-0 appearance-none cursor-pointer select-none",'data-[focus-visible="true"]:outline-2 data-[focus-visible="true"]:outline data-[focus-visible="true"]:outline-slate-100',"bg-transparent text-inherit",o&&["py-2"],e?.trigger),title:r("text-xl font-semibold",o&&"text-base",e?.title),subtitle:r("text-base",o&&"text-sm",e?.subtitle),titleWrapper:r("text-left",e?.titleWrapper),startContent:r("shrink-0",e?.startContent),content:r([o?"py-1":"py-2"],e?.content),indicator:e?.indicator,heading:e?.heading},...i,children:n})}function f(){return d(u,{className:"w-4 h-4"})}export{x as Accordion,f as AccordionItemArrow,x as default};
1
+ "use client";import{jsx as d}from"react/jsx-runtime";import{Accordion as a}from"@heroui/accordion";import{cn as r}from"../../utils/cn.js";import{DownArrow as u}from"../Icon/DownArrow.js";function x({children:n,variant:t,isCompact:o,className:l,itemClasses:e,...i}){return d(a,{variant:t,isCompact:o,dividerProps:{className:r("my-2 border-solid border-slate-200",t==="shadow"&&"border-slate-300")},className:r("px-0",t==="bordered"&&["px-4 py-4 rounded-xl border-2 border-slate-200"],t==="shadow"&&["px-4 py-4 rounded-xl bg-slate-100 shadow-slate-200 border border-slate-200"],l),itemClasses:{base:r("w-full py-2",'data-[disabled="true"]:pointer-events-none data-[disabled="true"]:opacity-50',t==="bordered"&&["rounded-xl py-0"],t==="splitted"&&["flex flex-col bg-slate-100 rounded-xl border border-slate-200"],o&&"py-0",e?.base),trigger:r("w-full h-full flex items-center gap-3 py-0 appearance-none cursor-pointer select-none",'data-[focus-visible="true"]:outline-2 data-[focus-visible="true"]:outline data-[focus-visible="true"]:outline-slate-100',"bg-transparent text-inherit",o&&["py-2"],e?.trigger),title:r("text-xl font-semibold",o&&"text-base",e?.title),subtitle:r("text-base",o&&"text-sm",e?.subtitle),titleWrapper:r("text-left",e?.titleWrapper),startContent:r("shrink-0",e?.startContent),content:r([o?"py-1":"py-2"],e?.content),indicator:e?.indicator,heading:e?.heading},...i,children:n})}function f(){return d(u,{className:"w-4 h-4"})}export{x as Accordion,f as AccordionItemArrow,x as default};
@@ -1,3 +1,3 @@
1
1
  export { Accordion, AccordionItemArrow } from './Accordion';
2
- export { AccordionItem } from '@nextui-org/accordion';
3
- export type { AccordionProps } from '@nextui-org/accordion';
2
+ export { AccordionItem } from '@heroui/accordion';
3
+ export type { AccordionProps } from '@heroui/accordion';
@@ -1 +1 @@
1
- import{Accordion as i,AccordionItemArrow as p}from"./Accordion.js";import{AccordionItem as A}from"@nextui-org/accordion";import"react/jsx-runtime";import"../../utils/cn.js";import"../Icon/DownArrow.js";export{i as Accordion,A as AccordionItem,p as AccordionItemArrow};
1
+ import{Accordion as i,AccordionItemArrow as p}from"./Accordion.js";import{AccordionItem as A}from"@heroui/accordion";import"react/jsx-runtime";import"../../utils/cn.js";import"../Icon/DownArrow.js";export{i as Accordion,A as AccordionItem,p as AccordionItemArrow};
@@ -1 +1 @@
1
- "use client";import{jsx as i}from"react/jsx-runtime";import{useId as y}from"react";import{AnimatePresence as V,motion as z}from"framer-motion";import{Badge as C}from"@nextui-org/badge";import{cn as l}from"../../utils/cn.js";const B={hidden:{opacity:0,scale:0},show:{opacity:1,scale:1}};function q({children:b,isInvisible:n,disableAnimation:d,placement:t="top-right",shape:o="rectangle",className:h,classNames:g,showOutline:x=!1,content:r,isDot:s,isOneChar:f,size:a="md",variant:e="solid",...m}){const c=y(),p=d?{duration:0}:{type:"spring",bounce:.3},_=String(r)?.length===1||f,w={sm:"text-xs",md:"text-sm",lg:"text-sm"},u=_?{sm:"w-4 h-4",md:"w-5 h-5",lg:"w-6 h-6"}[a]:{sm:"min-w-4 h-4",md:"min-w-5 h-5",lg:"min-w-6 h-6"}[a];return i(C,{...m,size:a,variant:e,isInvisible:n,isDot:s,classNames:{base:l("relative","inline-flex shrink-0",g?.base),badge:l("p-0","border-0","absolute z-10",[t==="top-right"&&o==="rectangle"&&"top-0.5 right-px",t==="top-right"&&o==="circle"&&"top-1 right-1",t==="bottom-right"&&o==="rectangle"&&"bottom-0.5 right-px",t==="bottom-right"&&o==="circle"&&"bottom-1 right-1",t==="top-left"&&o==="rectangle"&&"top-0.5 left-px",t==="top-left"&&o==="circle"&&"top-1 left-1",t==="bottom-left"&&o==="rectangle"&&"bottom-0.5 left-px",t==="bottom-left"&&o==="circle"&&"bottom-1 left-1"],[t==="top-right"&&"translate-x-1/2 -translate-y-1/2",t==="bottom-right"&&"translate-x-1/2 translate-y-1/2",t==="bottom-left"&&"-translate-x-1/2 translate-y-1/2",t==="top-left"&&"-translate-x-1/2 -translate-y-1/2"],g?.badge)},content:i(V,{children:!n&&i(z.span,{layout:!0,variants:B,initial:d?"show":"hidden",transition:p,animate:"show",exit:"hidden",className:l("flex flex-wrap","place-content-center","z-10","box-border","rounded-full","py-0 px-1","font-normal text-slate-900","select-none","whitespace-normal",e==="faded"?"text-slate-800":"text-white",!r||s?"w-3.5 h-3.5":u,typeof r=="string"&&r.length>1||s?"text-xs":w[a],[e==="solid"&&"bg-slate-800",e==="shadow"&&"bg-slate-800",e==="flat"&&"bg-slate-800/55",e==="faded"&&"bg-white"],[e==="faded"&&"border-2 border-solid border-slate-800",x&&e!=="faded"&&"border-2 border-solid border-white"],e==="shadow"&&"shadow-[0_0_0_0_rgba(0,0,0,0),_0_0_0_0_rgba(0,0,0,0),_0_10px_15px_-3px_rgba(30,41,59,0.3),_0_4px_6px_-4px_rgba(30,41,59,0.3)]",h),children:r},`badge-${c}`)}),children:b})}export{q as Badge,q as default};
1
+ "use client";import{jsx as i}from"react/jsx-runtime";import{useId as y}from"react";import{AnimatePresence as V,motion as z}from"framer-motion";import{Badge as C}from"@heroui/badge";import{cn as l}from"../../utils/cn.js";const B={hidden:{opacity:0,scale:0},show:{opacity:1,scale:1}};function q({children:b,isInvisible:n,disableAnimation:d,placement:t="top-right",shape:o="rectangle",className:h,classNames:g,showOutline:x=!1,content:r,isDot:s,isOneChar:f,size:a="md",variant:e="solid",...m}){const c=y(),p=d?{duration:0}:{type:"spring",bounce:.3},_=String(r)?.length===1||f,w={sm:"text-xs",md:"text-sm",lg:"text-sm"},u=_?{sm:"w-4 h-4",md:"w-5 h-5",lg:"w-6 h-6"}[a]:{sm:"min-w-4 h-4",md:"min-w-5 h-5",lg:"min-w-6 h-6"}[a];return i(C,{...m,size:a,variant:e,isInvisible:n,isDot:s,classNames:{base:l("relative","inline-flex shrink-0",g?.base),badge:l("p-0","border-0","absolute z-10",[t==="top-right"&&o==="rectangle"&&"top-0.5 right-px",t==="top-right"&&o==="circle"&&"top-1 right-1",t==="bottom-right"&&o==="rectangle"&&"bottom-0.5 right-px",t==="bottom-right"&&o==="circle"&&"bottom-1 right-1",t==="top-left"&&o==="rectangle"&&"top-0.5 left-px",t==="top-left"&&o==="circle"&&"top-1 left-1",t==="bottom-left"&&o==="rectangle"&&"bottom-0.5 left-px",t==="bottom-left"&&o==="circle"&&"bottom-1 left-1"],[t==="top-right"&&"translate-x-1/2 -translate-y-1/2",t==="bottom-right"&&"translate-x-1/2 translate-y-1/2",t==="bottom-left"&&"-translate-x-1/2 translate-y-1/2",t==="top-left"&&"-translate-x-1/2 -translate-y-1/2"],g?.badge)},content:i(V,{children:!n&&i(z.span,{layout:!0,variants:B,initial:d?"show":"hidden",transition:p,animate:"show",exit:"hidden",className:l("flex flex-wrap","place-content-center","z-10","box-border","rounded-full","py-0 px-1","font-normal text-slate-900","select-none","whitespace-normal",e==="faded"?"text-slate-800":"text-white",!r||s?"w-3.5 h-3.5":u,typeof r=="string"&&r.length>1||s?"text-xs":w[a],[e==="solid"&&"bg-slate-800",e==="shadow"&&"bg-slate-800",e==="flat"&&"bg-slate-800/55",e==="faded"&&"bg-white"],[e==="faded"&&"border-2 border-solid border-slate-800",x&&e!=="faded"&&"border-2 border-solid border-white"],e==="shadow"&&"shadow-[0_0_0_0_rgba(0,0,0,0),_0_0_0_0_rgba(0,0,0,0),_0_10px_15px_-3px_rgba(30,41,59,0.3),_0_4px_6px_-4px_rgba(30,41,59,0.3)]",h),children:r},`badge-${c}`)}),children:b})}export{q as Badge,q as default};
@@ -1,2 +1,2 @@
1
- import { BadgeProps as NextBadgeProps } from '@nextui-org/badge';
1
+ import { BadgeProps as NextBadgeProps } from '@heroui/badge';
2
2
  export type BadgeProps = Omit<NextBadgeProps, 'color | disableOutline'>;
@@ -1 +1 @@
1
- import{Badge as a}from"./Badge.js";import"react/jsx-runtime";import"react";import"framer-motion";import"@nextui-org/badge";import"../../utils/cn.js";export{a as Badge};
1
+ import{Badge as a}from"./Badge.js";import"react/jsx-runtime";import"react";import"framer-motion";import"@heroui/badge";import"../../utils/cn.js";export{a as Badge};
@@ -1 +1 @@
1
- "use client";import{jsxs as o,Fragment as f,jsx as e}from"react/jsx-runtime";import{useMemo as b,Suspense as x,useState as v}from"react";import{Accordion as w}from"../../Accordion/Accordion.js";import{AccordionItem as g}from"@nextui-org/accordion";import{Button as h}from"../../Button/Button.js";import"framer-motion";import{ModalOverlay as y}from"../../ModalOverlay/ModalOverlay.js";import{Modal as N}from"../../Modal/Modal.js";import{Drawer as k}from"../../Drawer/Drawer.js";import{Cross as C}from"../../Icon/Cross.js";import{CrossSmall as A}from"../../Icon/CrossSmall.js";import{Search as D}from"../../Icon/Search.js";import{FilterRenderer as I}from"../FilterRenderer/FilterRenderer.js";import{mergeFilters as S}from"../../../utils/collection/mergeFilters.js";import{FilterAccordionTitle as j}from"../FilterAccordionTitle/FilterAccordionTitle.js";import{EmptyResults as L}from"../EmptyResults/EmptyResults.js";import"../../../utils/cn.js";import"../../Icon/DownArrow.js";import"react-aria-components";import"../../DialogTrigger/DialogTrigger.js";import"../../../utils/env.js";import"../../../utils/swatch.js";import"../FilterRenderer/Renderer/Option.js";import"../../Checkbox/Checkbox.js";import"../FilterRenderer/Renderer/useOptionFilterToggle.js";import"../FilterRenderer/Renderer/Swatch.js";import"../FilterRenderer/Renderer/Swatch/SwatchColor.js";import"../../Icon/CheckIcon.js";import"../../../utils/color.js";import"../FilterRenderer/Renderer/Swatch/SwatchImage.js";import"../FilterRenderer/Renderer/Swatch/SwatchText.js";import"../FilterRenderer/Renderer/Boolean.js";import"../../RadioGroup/RadioGroup.js";import"../../Radio/Radio.js";import"../FilterRenderer/Renderer/Range.js";function M(){return e("svg",{width:"16",height:"16",viewBox:"0 0 16 16",fill:"none",xmlns:"http://www.w3.org/2000/svg",children:e("path",{d:"M4 6L8 10L12 6",stroke:"currentColor",strokeWidth:"1.5",strokeLinecap:"round",strokeLinejoin:"round"})})}function V({value:n,onChange:r}){const[l,c]=v(n),m=()=>{r(l.trim())};return o("div",{className:"relative",children:[e("input",{type:"search",value:l,onChange:i=>c(i.target.value),onKeyDown:i=>{i.key==="Enter"&&(i.preventDefault(),m())},onBlur:m,placeholder:"Search products...","aria-label":"Search products",autoComplete:"off",className:"w-full h-9 pl-8 pr-8 rounded-md border border-slate-300 bg-white text-sm text-slate-900 placeholder:text-slate-400 focus:outline-none focus:border-slate-400 [&::-webkit-search-cancel-button]:hidden"}),l&&e("button",{type:"button",onClick:()=>{c(""),r("")},"aria-label":"Clear search",className:"absolute right-2 top-1/2 -translate-y-1/2 p-0.5 rounded-full hover:bg-slate-100 transition-colors",children:e(A,{})}),e("span",{className:"absolute left-2.5 top-1/2 -translate-y-1/2 w-3.5 h-3.5 [&>svg]:w-full [&>svg]:h-full text-slate-400",children:e(D,{})})]})}function he({availableFilters:n,appliedFilters:r,onFilterChange:l,showMobileFilters:c,setShowMobileFilters:m,searchQuery:s,onSearchChange:a,totalItems:i}){const p=b(()=>S(r,n).filter(t=>t.includeInFilters!==!1),[r,n]),d=a&&e(V,{value:s??"",onChange:a},s),u=()=>p.length>0&&e(w,{selectionMode:"multiple",defaultSelectedKeys:"all",className:"pt-2 mt-3",showDivider:!1,itemClasses:{base:"py-1",title:"text-sm font-medium leading-7",trigger:"py-2 border-b border-gray-200",indicator:'data-[open="true"]:rotate-180',content:"pt-2 pb-3"},children:p.map(t=>e(g,{textValue:t.label,title:e(j,{filter:t,appliedFilters:r,onFilterChange:l}),disableIndicatorAnimation:!0,indicator:M,children:e("div",{className:"pt-2",children:e(I,{filter:t,onFilterChange:l,appliedFilters:r,variant:"sidebar"})})},t.code))});return o(f,{children:[o("div",{className:"hidden lg:flex lg:flex-col lg:basis-[16.5rem] lg:shrink-0",children:[d,u(),r.length>0&&e("button",{type:"button",className:"text-sm text-slate-500 hover:text-slate-700 transition-colors mt-4 mb-8",onClick:()=>l([]),children:"Clear all filters"})]}),e("div",{className:"lg:hidden",children:e(x,{children:e(y,{isOpen:c,onOpenChange:t=>m(t),isDismissable:!0,children:e(N,{children:e(k,{size:"30rem",direction:"left","aria-label":"Filters",className:"p-5",children:({close:t})=>o("div",{className:"flex flex-col w-full h-full overflow-hidden",children:[o("div",{className:"flex items-center w-full justify-between",children:[e("span",{className:"text-xl font-bold text-slate-800",children:"Filters"}),e(h,{variant:"ghost","aria-label":"Close",onPress:()=>t(),className:"justify-start size-auto p-0",children:e(C,{})})]}),o("div",{className:"flex flex-col flex-1 overflow-x-hidden overflow-y-auto [scrollbar-width:none] [-webkit-overflow-scrolling:touch] [&::-webkit-scrollbar]:hidden",children:[d&&e("div",{className:"mt-3",children:d}),u(),i===0&&e(L,{searchQuery:s,hasFilters:r.length>0,onClearFilters:()=>l([]),onClearSearch:s&&a?()=>a(""):void 0})]}),o("div",{className:"shrink-0 pt-3 flex flex-col gap-3 border-t border-gray-200",children:[r.length>0&&e("button",{type:"button",className:"text-sm py-2 text-slate-500 hover:text-slate-700 transition-colors",onClick:()=>l([]),children:"Clear all"}),e(h,{className:"w-full py-6 text-base rounded-xl",variant:"primary",onPress:()=>t(),children:"View Results"})]})]})})})})})})]})}export{he as CollectionFilters};
1
+ "use client";import{jsxs as o,Fragment as f,jsx as e}from"react/jsx-runtime";import{useMemo as b,Suspense as x,useState as v}from"react";import{Accordion as w}from"../../Accordion/Accordion.js";import{AccordionItem as g}from"@heroui/accordion";import{Button as h}from"../../Button/Button.js";import"framer-motion";import{ModalOverlay as y}from"../../ModalOverlay/ModalOverlay.js";import{Modal as N}from"../../Modal/Modal.js";import{Drawer as k}from"../../Drawer/Drawer.js";import{Cross as C}from"../../Icon/Cross.js";import{CrossSmall as A}from"../../Icon/CrossSmall.js";import{Search as D}from"../../Icon/Search.js";import{FilterRenderer as I}from"../FilterRenderer/FilterRenderer.js";import{mergeFilters as S}from"../../../utils/collection/mergeFilters.js";import{FilterAccordionTitle as j}from"../FilterAccordionTitle/FilterAccordionTitle.js";import{EmptyResults as L}from"../EmptyResults/EmptyResults.js";import"../../../utils/cn.js";import"../../Icon/DownArrow.js";import"react-aria-components";import"../../DialogTrigger/DialogTrigger.js";import"../../../utils/env.js";import"../../../utils/swatch.js";import"../FilterRenderer/Renderer/Option.js";import"../../Checkbox/Checkbox.js";import"../FilterRenderer/Renderer/useOptionFilterToggle.js";import"../FilterRenderer/Renderer/Swatch.js";import"../FilterRenderer/Renderer/Swatch/SwatchColor.js";import"../../Icon/CheckIcon.js";import"../../../utils/color.js";import"../FilterRenderer/Renderer/Swatch/SwatchImage.js";import"../FilterRenderer/Renderer/Swatch/SwatchText.js";import"../FilterRenderer/Renderer/Boolean.js";import"../../RadioGroup/RadioGroup.js";import"../../Radio/Radio.js";import"../FilterRenderer/Renderer/Range.js";function M(){return e("svg",{width:"16",height:"16",viewBox:"0 0 16 16",fill:"none",xmlns:"http://www.w3.org/2000/svg",children:e("path",{d:"M4 6L8 10L12 6",stroke:"currentColor",strokeWidth:"1.5",strokeLinecap:"round",strokeLinejoin:"round"})})}function V({value:n,onChange:r}){const[l,c]=v(n),m=()=>{r(l.trim())};return o("div",{className:"relative",children:[e("input",{type:"search",value:l,onChange:i=>c(i.target.value),onKeyDown:i=>{i.key==="Enter"&&(i.preventDefault(),m())},onBlur:m,placeholder:"Search products...","aria-label":"Search products",autoComplete:"off",className:"w-full h-9 pl-8 pr-8 rounded-md border border-slate-300 bg-white text-sm text-slate-900 placeholder:text-slate-400 focus:outline-none focus:border-slate-400 [&::-webkit-search-cancel-button]:hidden"}),l&&e("button",{type:"button",onClick:()=>{c(""),r("")},"aria-label":"Clear search",className:"absolute right-2 top-1/2 -translate-y-1/2 p-0.5 rounded-full hover:bg-slate-100 transition-colors",children:e(A,{})}),e("span",{className:"absolute left-2.5 top-1/2 -translate-y-1/2 w-3.5 h-3.5 [&>svg]:w-full [&>svg]:h-full text-slate-400",children:e(D,{})})]})}function he({availableFilters:n,appliedFilters:r,onFilterChange:l,showMobileFilters:c,setShowMobileFilters:m,searchQuery:s,onSearchChange:a,totalItems:i}){const p=b(()=>S(r,n).filter(t=>t.includeInFilters!==!1),[r,n]),d=a&&e(V,{value:s??"",onChange:a},s),u=()=>p.length>0&&e(w,{selectionMode:"multiple",defaultSelectedKeys:"all",className:"pt-2 mt-3",showDivider:!1,itemClasses:{base:"py-1",title:"text-sm font-medium leading-7",trigger:"py-2 border-b border-gray-200",indicator:'data-[open="true"]:rotate-180',content:"pt-2 pb-3"},children:p.map(t=>e(g,{textValue:t.label,title:e(j,{filter:t,appliedFilters:r,onFilterChange:l}),disableIndicatorAnimation:!0,indicator:M,children:e("div",{className:"pt-2",children:e(I,{filter:t,onFilterChange:l,appliedFilters:r,variant:"sidebar"})})},t.code))});return o(f,{children:[o("div",{className:"hidden lg:flex lg:flex-col lg:basis-[16.5rem] lg:shrink-0",children:[d,u(),r.length>0&&e("button",{type:"button",className:"text-sm text-slate-500 hover:text-slate-700 transition-colors mt-4 mb-8",onClick:()=>l([]),children:"Clear all filters"})]}),e("div",{className:"lg:hidden",children:e(x,{children:e(y,{isOpen:c,onOpenChange:t=>m(t),isDismissable:!0,children:e(N,{children:e(k,{size:"30rem",direction:"left","aria-label":"Filters",className:"p-5",children:({close:t})=>o("div",{className:"flex flex-col w-full h-full overflow-hidden",children:[o("div",{className:"flex items-center w-full justify-between",children:[e("span",{className:"text-xl font-bold text-slate-800",children:"Filters"}),e(h,{variant:"ghost","aria-label":"Close",onPress:()=>t(),className:"justify-start size-auto p-0",children:e(C,{})})]}),o("div",{className:"flex flex-col flex-1 overflow-x-hidden overflow-y-auto [scrollbar-width:none] [-webkit-overflow-scrolling:touch] [&::-webkit-scrollbar]:hidden",children:[d&&e("div",{className:"mt-3",children:d}),u(),i===0&&e(L,{searchQuery:s,hasFilters:r.length>0,onClearFilters:()=>l([]),onClearSearch:s&&a?()=>a(""):void 0})]}),o("div",{className:"shrink-0 pt-3 flex flex-col gap-3 border-t border-gray-200",children:[r.length>0&&e("button",{type:"button",className:"text-sm py-2 text-slate-500 hover:text-slate-700 transition-colors",onClick:()=>l([]),children:"Clear all"}),e(h,{className:"w-full py-6 text-base rounded-xl",variant:"primary",onPress:()=>t(),children:"View Results"})]})]})})})})})})]})}export{he as CollectionFilters};
@@ -1 +1 @@
1
- import{CollectionFilters as L}from"./CollectionFilters.js";import"react/jsx-runtime";import"react";import"../../Accordion/Accordion.js";import"@nextui-org/accordion";import"../../../utils/cn.js";import"../../Icon/DownArrow.js";import"../../Button/Button.js";import"react-aria-components";import"framer-motion";import"../../ModalOverlay/ModalOverlay.js";import"../../DialogTrigger/DialogTrigger.js";import"../../Modal/Modal.js";import"../../Drawer/Drawer.js";import"../../Icon/Cross.js";import"../../Icon/CrossSmall.js";import"../../Icon/Search.js";import"../FilterRenderer/FilterRenderer.js";import"../../../utils/env.js";import"../../../utils/swatch.js";import"../FilterRenderer/Renderer/Option.js";import"../../Checkbox/Checkbox.js";import"../FilterRenderer/Renderer/useOptionFilterToggle.js";import"../FilterRenderer/Renderer/Swatch.js";import"../FilterRenderer/Renderer/Swatch/SwatchColor.js";import"../../Icon/CheckIcon.js";import"../../../utils/color.js";import"../FilterRenderer/Renderer/Swatch/SwatchImage.js";import"../FilterRenderer/Renderer/Swatch/SwatchText.js";import"../FilterRenderer/Renderer/Boolean.js";import"../../RadioGroup/RadioGroup.js";import"../../Radio/Radio.js";import"../FilterRenderer/Renderer/Range.js";import"../../../utils/collection/mergeFilters.js";import"../FilterAccordionTitle/FilterAccordionTitle.js";import"../EmptyResults/EmptyResults.js";export{L as CollectionFilters};
1
+ import{CollectionFilters as L}from"./CollectionFilters.js";import"react/jsx-runtime";import"react";import"../../Accordion/Accordion.js";import"@heroui/accordion";import"../../../utils/cn.js";import"../../Icon/DownArrow.js";import"../../Button/Button.js";import"react-aria-components";import"framer-motion";import"../../ModalOverlay/ModalOverlay.js";import"../../DialogTrigger/DialogTrigger.js";import"../../Modal/Modal.js";import"../../Drawer/Drawer.js";import"../../Icon/Cross.js";import"../../Icon/CrossSmall.js";import"../../Icon/Search.js";import"../FilterRenderer/FilterRenderer.js";import"../../../utils/env.js";import"../../../utils/swatch.js";import"../FilterRenderer/Renderer/Option.js";import"../../Checkbox/Checkbox.js";import"../FilterRenderer/Renderer/useOptionFilterToggle.js";import"../FilterRenderer/Renderer/Swatch.js";import"../FilterRenderer/Renderer/Swatch/SwatchColor.js";import"../../Icon/CheckIcon.js";import"../../../utils/color.js";import"../FilterRenderer/Renderer/Swatch/SwatchImage.js";import"../FilterRenderer/Renderer/Swatch/SwatchText.js";import"../FilterRenderer/Renderer/Boolean.js";import"../../RadioGroup/RadioGroup.js";import"../../Radio/Radio.js";import"../FilterRenderer/Renderer/Range.js";import"../../../utils/collection/mergeFilters.js";import"../FilterAccordionTitle/FilterAccordionTitle.js";import"../EmptyResults/EmptyResults.js";export{L as CollectionFilters};
@@ -1 +1 @@
1
- import{AttributeDataProvider as V,useAttributeData as W}from"./AttributeDataProvider/AttributeDataProvider.js";import{EmptyResults as Y}from"./EmptyResults/EmptyResults.js";import{ProductPrice as _}from"./ProductPrice/ProductPrice.js";import{FilterRenderer as rr}from"./FilterRenderer/FilterRenderer.js";import{CollectionFilterChips as tr}from"./CollectionFilterChips/CollectionFilterChips.js";import{FilterAccordionTitle as pr}from"./FilterAccordionTitle/FilterAccordionTitle.js";import{ProductCardSwatches as er}from"./ProductCardSwatches/ProductCardSwatches.js";import{CollectionFilters as cr}from"./CollectionFilters/CollectionFilters.js";import{CollectionToolbar as xr}from"./CollectionToolbar/CollectionToolbar.js";import"react/jsx-runtime";import"react";import"../../utils/env.js";import"../../utils/swatch.js";import"./FilterRenderer/Renderer/Option.js";import"../Checkbox/Checkbox.js";import"react-aria-components";import"../../utils/cn.js";import"./FilterRenderer/Renderer/useOptionFilterToggle.js";import"./FilterRenderer/Renderer/Swatch.js";import"./FilterRenderer/Renderer/Swatch/SwatchColor.js";import"../Icon/CheckIcon.js";import"../../utils/color.js";import"./FilterRenderer/Renderer/Swatch/SwatchImage.js";import"./FilterRenderer/Renderer/Swatch/SwatchText.js";import"./FilterRenderer/Renderer/Boolean.js";import"../RadioGroup/RadioGroup.js";import"../Radio/Radio.js";import"./FilterRenderer/Renderer/Range.js";import"../Button/Button.js";import"framer-motion";import"../Icon/CrossSmall.js";import"../../utils/collection/getAppliedFilterChips.js";import"../../utils/collection/removeFilterChip.js";import"../Accordion/Accordion.js";import"@nextui-org/accordion";import"../Icon/DownArrow.js";import"../ModalOverlay/ModalOverlay.js";import"../DialogTrigger/DialogTrigger.js";import"../Modal/Modal.js";import"../Drawer/Drawer.js";import"../Icon/Cross.js";import"../Icon/Search.js";import"../../utils/collection/mergeFilters.js";import"../Select/Option.js";import"react-stately";import"../Select/Select.js";import"../Select/ButtonSelect.js";import"react-aria";import"@react-aria/interactions";import"../Select/parts.js";import"../Select/ComboBoxSelect.js";import"../Loader/Pulse.js";import"../Select/MultiButtonSelect.js";import"../Icon/FilterIcon.js";export{V as AttributeDataProvider,tr as CollectionFilterChips,cr as CollectionFilters,xr as CollectionToolbar,Y as EmptyResults,pr as FilterAccordionTitle,rr as FilterRenderer,er as ProductCardSwatches,_ as ProductPrice,W as useAttributeData};
1
+ import{AttributeDataProvider as V,useAttributeData as W}from"./AttributeDataProvider/AttributeDataProvider.js";import{EmptyResults as Y}from"./EmptyResults/EmptyResults.js";import{ProductPrice as _}from"./ProductPrice/ProductPrice.js";import{FilterRenderer as rr}from"./FilterRenderer/FilterRenderer.js";import{CollectionFilterChips as tr}from"./CollectionFilterChips/CollectionFilterChips.js";import{FilterAccordionTitle as pr}from"./FilterAccordionTitle/FilterAccordionTitle.js";import{ProductCardSwatches as er}from"./ProductCardSwatches/ProductCardSwatches.js";import{CollectionFilters as cr}from"./CollectionFilters/CollectionFilters.js";import{CollectionToolbar as xr}from"./CollectionToolbar/CollectionToolbar.js";import"react/jsx-runtime";import"react";import"../../utils/env.js";import"../../utils/swatch.js";import"./FilterRenderer/Renderer/Option.js";import"../Checkbox/Checkbox.js";import"react-aria-components";import"../../utils/cn.js";import"./FilterRenderer/Renderer/useOptionFilterToggle.js";import"./FilterRenderer/Renderer/Swatch.js";import"./FilterRenderer/Renderer/Swatch/SwatchColor.js";import"../Icon/CheckIcon.js";import"../../utils/color.js";import"./FilterRenderer/Renderer/Swatch/SwatchImage.js";import"./FilterRenderer/Renderer/Swatch/SwatchText.js";import"./FilterRenderer/Renderer/Boolean.js";import"../RadioGroup/RadioGroup.js";import"../Radio/Radio.js";import"./FilterRenderer/Renderer/Range.js";import"../Button/Button.js";import"framer-motion";import"../Icon/CrossSmall.js";import"../../utils/collection/getAppliedFilterChips.js";import"../../utils/collection/removeFilterChip.js";import"../Accordion/Accordion.js";import"@heroui/accordion";import"../Icon/DownArrow.js";import"../ModalOverlay/ModalOverlay.js";import"../DialogTrigger/DialogTrigger.js";import"../Modal/Modal.js";import"../Drawer/Drawer.js";import"../Icon/Cross.js";import"../Icon/Search.js";import"../../utils/collection/mergeFilters.js";import"../Select/Option.js";import"react-stately";import"../Select/Select.js";import"../Select/ButtonSelect.js";import"react-aria";import"@react-aria/interactions";import"../Select/parts.js";import"../Select/ComboBoxSelect.js";import"../Loader/Pulse.js";import"../Select/MultiButtonSelect.js";import"../Icon/FilterIcon.js";export{V as AttributeDataProvider,tr as CollectionFilterChips,cr as CollectionFilters,xr as CollectionToolbar,Y as EmptyResults,pr as FilterAccordionTitle,rr as FilterRenderer,er as ProductCardSwatches,_ as ProductPrice,W as useAttributeData};
@@ -1,3 +1,3 @@
1
- import { PaginationProps } from '@nextui-org/pagination';
1
+ import { PaginationProps } from '@heroui/pagination';
2
2
  export declare function Pagination({ showControls, className, classNames, ...rest }: PaginationProps): import("react/jsx-runtime").JSX.Element;
3
3
  export default Pagination;
@@ -1 +1 @@
1
- "use client";import{jsx as s}from"react/jsx-runtime";import{Pagination as n}from"@nextui-org/pagination";import{cn as t}from"../../utils/cn.js";const r=t("w-8 h-8 flex justify-center items-center cursor-pointer shadow-none","border border-slate-200 data-[disabled=true]:border-0",'rounded transition-colors duration-300 data-[focus-visible="true"]:outline-white data-[focus-visible="true"]:outline-offset-4',"hover:bg-slate-50 data-[disabled=true]:bg-slate-100 data-[disabled=true]:text-slate-400","[&>svg]:w-4.5 [&>svg]:h-auto");function c({showControls:a=!0,className:o,classNames:e,...i}){return s(n,{showControls:a,className:t("text-slate-600",o),classNames:{wrapper:t("flex flex-nowrap gap-2 lg:gap-3",e?.wrapper),item:t("w-8 h-8 flex text-sm justify-center items-center cursor-pointer rounded shadow-none","border-solid border border-slate-100 bg-transparent transition-colors duration-300",'data-[active=true]:bg-slate-500 data-[active=true]:text-white data-[focus-visible="true"]:outline-white data-[focus-visible="true"]:outline-offset-4',"[&[data-hover=true]:not([data-active=true])]:border-slate-200",e?.item),prev:t(r,e?.prev),next:t(r,e?.next),ellipsis:t("w-4 h-4.5 scale-110 translate-y-1",e?.ellipsis)},...i})}export{c as Pagination,c as default};
1
+ "use client";import{jsx as s}from"react/jsx-runtime";import{Pagination as n}from"@heroui/pagination";import{cn as t}from"../../utils/cn.js";const r=t("w-8 h-8 flex justify-center items-center cursor-pointer shadow-none","border border-slate-200 data-[disabled=true]:border-0",'rounded transition-colors duration-300 data-[focus-visible="true"]:outline-white data-[focus-visible="true"]:outline-offset-4',"hover:bg-slate-50 data-[disabled=true]:bg-slate-100 data-[disabled=true]:text-slate-400","[&>svg]:w-4.5 [&>svg]:h-auto");function c({showControls:a=!0,className:o,classNames:e,...i}){return s(n,{showControls:a,className:t("text-slate-600",o),classNames:{wrapper:t("flex flex-nowrap gap-2 lg:gap-3",e?.wrapper),item:t("w-8 h-8 flex text-sm justify-center items-center cursor-pointer rounded shadow-none","border-solid border border-slate-100 bg-transparent transition-colors duration-300",'data-[active=true]:bg-slate-500 data-[active=true]:text-white data-[focus-visible="true"]:outline-white data-[focus-visible="true"]:outline-offset-4',"[&[data-hover=true]:not([data-active=true])]:border-slate-200",e?.item),prev:t(r,e?.prev),next:t(r,e?.next),ellipsis:t("w-4 h-4.5 scale-110 translate-y-1",e?.ellipsis)},...i})}export{c as Pagination,c as default};
@@ -1 +1 @@
1
- import{Pagination as p}from"./Pagination.js";import"react/jsx-runtime";import"@nextui-org/pagination";import"../../utils/cn.js";export{p as Pagination};
1
+ import{Pagination as p}from"./Pagination.js";import"react/jsx-runtime";import"@heroui/pagination";import"../../utils/cn.js";export{p as Pagination};
@@ -1,2 +1,2 @@
1
- export declare const Tooltip: import('react').ForwardRefExoticComponent<Omit<import('@nextui-org/tooltip').TooltipProps, "ref"> & import('react').RefAttributes<HTMLDivElement>>;
1
+ export declare const Tooltip: import('react').ForwardRefExoticComponent<Omit<import('@heroui/tooltip').TooltipProps, "ref"> & import('react').RefAttributes<HTMLDivElement>>;
2
2
  export default Tooltip;
@@ -1 +1 @@
1
- "use client";import{jsx as n}from"react/jsx-runtime";import{forwardRef as f}from"react";import{Tooltip as b}from"@nextui-org/tooltip";import{cn as t}from"../../utils/cn.js";const i=f(function({classNames:e,placement:a,showArrow:o=!1,...r},l){return n(b,{classNames:{base:[t(["z-0 relative bg-transparent outline-none","focus-visible:z-10 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-slate-500","before:absolute before:rotate-45 before:w-2.5 before:h-2.5 before:rounded-sm before:bg-slate-500 before:shadow-sm","before:hidden data-[arrow=true]:before:block","data-[placement=bottom]:before:-top-1 data-[placement=bottom]:before:left-1/2 data-[placement=bottom]:before:rotate-45 data-[placement=bottom]:before:-translate-x-1/2","data-[placement=bottom-end]:before:-top-1 data-[placement=bottom-end]:before:left-1/2 data-[placement=bottom-end]:before:rotate-45 data-[placement=bottom-end]:before:-translate-x-1/2","data-[placement=bottom-start]:before:-top-1 data-[placement=bottom-start]:before:left-1/2 data-[placement=bottom-start]:before:rotate-45 data-[placement=bottom-start]:before:-translate-x-1/2","data-[placement=left]:before:-right-1 data-[placement=left]:before:top-1/2 data-[placement=left]:before:-translate-y-1/2","data-[placement=left-end]:before:-right-1 data-[placement=left-end]:before:top-1/2 data-[placement=left-end]:before:-translate-y-1/2","data-[placement=left-start]:before:-right-1 data-[placement=left-start]:before:top-1/2 data-[placement=left-start]:before:-translate-y-1/2","data-[placement=right]:before:-left-1 data-[placement=right]:before:top-1/2 data-[placement=right]:before:-translate-y-1/2","data-[placement=right-end]:before:-left-1 data-[placement=right-end]:before:top-1/2 data-[placement=right-end]:before:-translate-y-1/2","data-[placement=right-start]:before:-left-1 data-[placement=right-start]:before:top-1/2 data-[placement=right-start]:before:-translate-y-1/2","data-[placement=top]:before:-bottom-1 data-[placement=top]:before:left-1/2 data-[placement=top]:before:rotate-45 data-[placement=top]:before:-translate-x-1/2","data-[placement=top-end]:before:-bottom-1 data-[placement=top-end]:before:left-1/2 data-[placement=top-end]:before:rotate-45 data-[placement=top-end]:before:-translate-x-1/2","data-[placement=top-start]:before:-bottom-1 data-[placement=top-start]:before:left-1/2 data-[placement=top-start]:before:rotate-45 data-[placement=top-start]:before:-translate-x-1/2",e?.base])],content:[t(["inline-flex flex-col items-center justify-center outline-none","w-full py-1 px-2.5 z-10 box-border bg-slate-500 rounded-md shadow-sm","text-sm text-white subpixel-antialiased",e?.content])]},placement:a,showArrow:o,...r,ref:l})});export{i as Tooltip,i as default};
1
+ "use client";import{jsx as n}from"react/jsx-runtime";import{forwardRef as f}from"react";import{Tooltip as b}from"@heroui/tooltip";import{cn as t}from"../../utils/cn.js";const i=f(function({classNames:e,placement:a,showArrow:o=!1,...r},l){return n(b,{classNames:{base:[t(["z-0 relative bg-transparent outline-none","focus-visible:z-10 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-slate-500","before:absolute before:rotate-45 before:w-2.5 before:h-2.5 before:rounded-sm before:bg-slate-500 before:shadow-sm","before:hidden data-[arrow=true]:before:block","data-[placement=bottom]:before:-top-1 data-[placement=bottom]:before:left-1/2 data-[placement=bottom]:before:rotate-45 data-[placement=bottom]:before:-translate-x-1/2","data-[placement=bottom-end]:before:-top-1 data-[placement=bottom-end]:before:left-1/2 data-[placement=bottom-end]:before:rotate-45 data-[placement=bottom-end]:before:-translate-x-1/2","data-[placement=bottom-start]:before:-top-1 data-[placement=bottom-start]:before:left-1/2 data-[placement=bottom-start]:before:rotate-45 data-[placement=bottom-start]:before:-translate-x-1/2","data-[placement=left]:before:-right-1 data-[placement=left]:before:top-1/2 data-[placement=left]:before:-translate-y-1/2","data-[placement=left-end]:before:-right-1 data-[placement=left-end]:before:top-1/2 data-[placement=left-end]:before:-translate-y-1/2","data-[placement=left-start]:before:-right-1 data-[placement=left-start]:before:top-1/2 data-[placement=left-start]:before:-translate-y-1/2","data-[placement=right]:before:-left-1 data-[placement=right]:before:top-1/2 data-[placement=right]:before:-translate-y-1/2","data-[placement=right-end]:before:-left-1 data-[placement=right-end]:before:top-1/2 data-[placement=right-end]:before:-translate-y-1/2","data-[placement=right-start]:before:-left-1 data-[placement=right-start]:before:top-1/2 data-[placement=right-start]:before:-translate-y-1/2","data-[placement=top]:before:-bottom-1 data-[placement=top]:before:left-1/2 data-[placement=top]:before:rotate-45 data-[placement=top]:before:-translate-x-1/2","data-[placement=top-end]:before:-bottom-1 data-[placement=top-end]:before:left-1/2 data-[placement=top-end]:before:rotate-45 data-[placement=top-end]:before:-translate-x-1/2","data-[placement=top-start]:before:-bottom-1 data-[placement=top-start]:before:left-1/2 data-[placement=top-start]:before:rotate-45 data-[placement=top-start]:before:-translate-x-1/2",e?.base])],content:[t(["inline-flex flex-col items-center justify-center outline-none","w-full py-1 px-2.5 z-10 box-border bg-slate-500 rounded-md shadow-sm","text-sm text-white subpixel-antialiased",e?.content])]},placement:a,showArrow:o,...r,ref:l})});export{i as Tooltip,i as default};
@@ -1,2 +1,2 @@
1
- import { TooltipProps as UITooltipProps } from '@nextui-org/tooltip';
1
+ import { TooltipProps as UITooltipProps } from '@heroui/tooltip';
2
2
  export type TooltipProps = UITooltipProps;
@@ -1 +1 @@
1
- import{Tooltip as e}from"./Tooltip.js";import"react/jsx-runtime";import"react";import"@nextui-org/tooltip";import"../../utils/cn.js";export{e as Tooltip};
1
+ import{Tooltip as e}from"./Tooltip.js";import"react/jsx-runtime";import"react";import"@heroui/tooltip";import"../../utils/cn.js";export{e as Tooltip};
@@ -0,0 +1,290 @@
1
+ # External Link Detection Migration
2
+
3
+ **Date:** 2026-01-14
4
+ **Affects:** `@clicktap/ui`, `apps/frontend`
5
+ **Breaking:** No (additive, but requires provider setup for SSR)
6
+ **Classification:** IMPROVEMENT
7
+
8
+
9
+ ## Dependencies
10
+
11
+ *None*
12
+
13
+ ## Summary
14
+
15
+ The `Link` component from `@clicktap/ui` now automatically detects external URLs and applies `target="_blank"` and `rel="noopener noreferrer"` attributes. This requires setting up `LinkProvider` for proper SSR support.
16
+
17
+ ## Changes
18
+
19
+ 1. `Link` component auto-detects external URLs
20
+ 2. New `LinkProvider` for SSR base URL configuration
21
+ 3. New `isExternalUrl` utility function
22
+ 4. Layout files must pass `baseUrl` from host header
23
+
24
+ ## Setup
25
+
26
+ ### 1. Update Layouts to Pass baseUrl
27
+
28
+ In your Next.js layout files (Server Components), extract the host header and pass it to Providers:
29
+
30
+ ```tsx
31
+ // app/(base)/layout.tsx
32
+ import { headers } from 'next/headers';
33
+
34
+ export default function Layout({ children }) {
35
+ const h = headers();
36
+ const baseUrl = `https://${h.get('host')}`;
37
+
38
+ return (
39
+ <Providers baseUrl={baseUrl}>
40
+ {children}
41
+ </Providers>
42
+ );
43
+ }
44
+ ```
45
+
46
+ ```tsx
47
+ // app/(checkout)/layout.tsx
48
+ import { headers } from 'next/headers';
49
+
50
+ export default function CheckoutLayout({ children }) {
51
+ const h = headers();
52
+ const baseUrl = `https://${h.get('host')}`;
53
+
54
+ return (
55
+ <Providers baseUrl={baseUrl}>
56
+ {children}
57
+ </Providers>
58
+ );
59
+ }
60
+ ```
61
+
62
+ ### 2. Update Providers Component
63
+
64
+ Add the `baseUrl` prop and wrap with `LinkProvider`:
65
+
66
+ ```tsx
67
+ // providers/Providers.tsx
68
+ import { LinkProvider } from '@clicktap/ui/components/Link';
69
+
70
+ type Props = {
71
+ baseUrl?: string;
72
+ children: ReactNode;
73
+ // ... other props
74
+ };
75
+
76
+ export function Providers({ baseUrl, children, ...props }: Props) {
77
+ return (
78
+ <LinkProvider config={{ baseUrl }}>
79
+ {/* other providers */}
80
+ {children}
81
+ </LinkProvider>
82
+ );
83
+ }
84
+ ```
85
+
86
+ ## How It Works
87
+
88
+ ### Detection Logic
89
+
90
+ - **Relative URLs** (`/path`, `#anchor`) → Internal (no attributes added)
91
+ - **Absolute URLs** → Compared against current host
92
+ - Same host → Internal
93
+ - Different host → External (adds `target="_blank"` and `rel="noopener noreferrer"`)
94
+
95
+ ### Host Detection
96
+
97
+ - **Client-side:** Uses `window.location.host` automatically
98
+ - **Server-side (SSR):** Uses `baseUrl` from `LinkProvider`
99
+
100
+ This ensures external links are correctly identified during both SSR and client-side rendering.
101
+
102
+ ## Usage
103
+
104
+ No changes needed in component code. The `Link` component handles detection automatically:
105
+
106
+ ```tsx
107
+ import { Link } from '@clicktap/ui/components/Link';
108
+
109
+ // Internal - renders normally
110
+ <Link href="/products">Products</Link>
111
+
112
+ // External - automatically gets target="_blank" rel="noopener noreferrer"
113
+ <Link href="https://google.com">Google</Link>
114
+
115
+ // Anchor - renders normally
116
+ <Link href="#section">Jump to section</Link>
117
+ ```
118
+
119
+ ### Override Behavior
120
+
121
+ You can override the automatic behavior if needed:
122
+
123
+ ```tsx
124
+ // Force internal link behavior on external URL
125
+ <Link href="https://subdomain.mysite.com" target="_self">
126
+ Subdomain
127
+ </Link>
128
+
129
+ // Force external link behavior on internal URL
130
+ <Link href="/download" target="_blank" rel="noopener noreferrer">
131
+ Download
132
+ </Link>
133
+ ```
134
+
135
+ ## Utility Function
136
+
137
+ The `isExternalUrl` utility is exported for use outside the Link component:
138
+
139
+ ```tsx
140
+ import { isExternalUrl } from '@clicktap/ui/components/Link';
141
+
142
+ const external = isExternalUrl('https://google.com', 'mysite.com');
143
+ // true
144
+
145
+ const internal = isExternalUrl('/products', 'mysite.com');
146
+ // false
147
+ ```
148
+
149
+ ## Files Changed
150
+
151
+ - `libs/ui/src/components/Link/Link.tsx` - Auto-detection logic
152
+ - `libs/ui/src/components/Link/LinkContext.tsx` - New provider
153
+ - `libs/ui/src/components/Link/utils.ts` - `isExternalUrl` utility
154
+ - `libs/ui/src/components/Link/index.ts` - Updated exports
155
+ - `apps/frontend/app/(base)/layout.tsx` - Pass baseUrl
156
+ - `apps/frontend/app/(checkout)/layout.tsx` - Pass baseUrl
157
+ - `apps/frontend/providers/Providers.tsx` - Add LinkProvider
158
+
159
+ ## Migration Checklist
160
+
161
+ 1. [ ] Update `libs/ui` to latest version
162
+ 2. [ ] Add `baseUrl` prop to Providers component
163
+ 3. [ ] Wrap app with `LinkProvider`
164
+ 4. [ ] Update all layout files to pass `baseUrl` from host header
165
+ 5. [ ] Test external links open in new tab
166
+ 6. [ ] Test internal links work normally
167
+ 7. [ ] Verify SSR renders correct attributes
168
+
169
+ ---
170
+
171
+ # LinkAsButton → @clicktap/ui
172
+
173
+ **Date:** 2026-02-06
174
+
175
+ ## Summary
176
+
177
+ `LinkAsButton` has been promoted from `apps/frontend/components/LinkAsButton/` into the shared UI library. `Button` also gains `primary`/`secondary`/`tertiary` variant aliases.
178
+
179
+ ## Changes
180
+
181
+ ### `LinkAsButton` promoted to shared UI library
182
+
183
+ - **Old import:** `import { LinkAsButton } from '../LinkAsButton/LinkAsButton'` (relative, app-level)
184
+ - **New import:** `import { LinkAsButton } from '@clicktap/ui/components/LinkAsButton'`
185
+ - The `[k: string]: any` catch-all type is removed — proper typing via `LinkProps` + `Pick<ButtonProps, 'variant' | 'size'>`
186
+ - Now supports all Button variants (`solid`, `outline`, `ghost`) plus aliases (`primary`, `secondary`, `tertiary`), `size` prop (`sm`, `md`, `lg`), and `isLoading`
187
+ - `apps/frontend/components/LinkAsButton/` has been deleted
188
+
189
+ ### Breaking: `LinkAsButton` default behavior changed
190
+
191
+ The old local component had no default variant — it rendered as a transparent link with base layout classes. The new shared component defaults to `variant="solid"` and `size="md"`:
192
+
193
+ | Property | Old (local) | New (`@clicktap/ui`) |
194
+ |----------|-------------|----------------------|
195
+ | `variant` | none (transparent background, transparent border) | `solid` (dark background, white text) |
196
+ | `size` | none (no height constraint) | `md` (`h-10` = 40px height) |
197
+
198
+ **Impact:** Any `<LinkAsButton>` without an explicit `variant` prop now renders as a dark solid button with a 40px height constraint. This is especially problematic when wrapping images — the image gets constrained inside a styled button container.
199
+
200
+ **Fix:** Use `variant="ghost"` for transparent links, or use `<Link>` directly when button styling isn't appropriate (e.g., clickable product images):
201
+
202
+ ```tsx
203
+ // Before: image wrapped in default LinkAsButton (was transparent, now solid)
204
+ <LinkAsButton href={url}>
205
+ <Image ... />
206
+ </LinkAsButton>
207
+
208
+ // After: use plain Link for image wrappers
209
+ <Link href={url}>
210
+ <Image ... />
211
+ </Link>
212
+
213
+ // After: use ghost variant if button semantics are needed
214
+ <LinkAsButton variant="ghost" href={url}>
215
+ Text link
216
+ </LinkAsButton>
217
+ ```
218
+
219
+ ### Button variant aliases
220
+
221
+ - `Button` now accepts `primary`, `secondary`, and `tertiary` as variant values
222
+ - `primary` → `solid`, `secondary` → `outline`, `tertiary` → `ghost`
223
+ - Existing `solid`, `outline`, `ghost` values continue to work unchanged
224
+
225
+ ---
226
+
227
+ # Image Component: Remove `width: 'auto'` Auto-Styling
228
+
229
+ **Date:** 2026-02-08
230
+ **Affects:** `@clicktap/ui` Image component, all consumers
231
+ **Breaking:** Yes - images using `width`/`height` props may render differently
232
+
233
+ ## Summary
234
+
235
+ The `@clicktap/ui` Image component previously auto-prepended `{ width: 'auto', height: 'auto' }` to the style prop when `width` and `height` props were provided (not `fill`). The `width: 'auto'` has been removed because it overrides the HTML `width` attribute via CSS, causing images to render at their natural intrinsic size instead of the dimensions specified by the props.
236
+
237
+ ## What Changed
238
+
239
+ ```diff
240
+ const imageStyle = hasSizedProps
241
+ - ? { width: 'auto' as const, height: 'auto' as const, ...style }
242
+ + ? { height: 'auto' as const, ...style }
243
+ : style;
244
+ ```
245
+
246
+ **Before:** Images with `width={100} height={100}` and no explicit `style` rendered at their natural resolution (CSS `width: auto` overrode the HTML attribute).
247
+
248
+ **After:** Images render at the width specified by the `width` prop. `height: 'auto'` preserves the natural aspect ratio.
249
+
250
+ ## Migration
251
+
252
+ ### Review Image usages without explicit `style` prop
253
+
254
+ Any `<Image>` that passes `width` and `height` props without a `style` override will now render at the `width` prop size instead of the natural image size. In most cases this is the correct behavior.
255
+
256
+ If the natural-size behavior was intentional, restore it with an explicit style:
257
+
258
+ ```tsx
259
+ <Image width={100} height={100} style={{ width: 'auto', height: 'auto' }} ... />
260
+ ```
261
+
262
+ ### Add aspect ratio control where needed
263
+
264
+ If the displayed image needs a specific aspect ratio that differs from the source image's natural ratio, add CSS classes. Use the actual source image dimensions to determine the ratio (e.g., 960x1200 images → `aspect-[4/5]`):
265
+
266
+ ```tsx
267
+ // Explicit aspect ratio matching source images (e.g., 960x1200 = 4:5)
268
+ <Image
269
+ width={100} height={125}
270
+ className="aspect-[4/5] object-cover"
271
+ style={{ width: '100%', height: 'auto' }}
272
+ />
273
+
274
+ // Fill container width, natural aspect ratio (common pattern, no extra classes needed)
275
+ <Image
276
+ width={400} height={300}
277
+ style={{ width: '100%', height: 'auto' }}
278
+ />
279
+ ```
280
+
281
+ ### Affected areas to check
282
+
283
+ Visually inspect any page that displays product images:
284
+ - Cart drawer (mini-cart)
285
+ - Cart page
286
+ - Checkout order summary
287
+ - Checkout success / order confirmation
288
+ - Category listing pages
289
+ - Product collection components
290
+ - Account order history
@@ -0,0 +1,142 @@
1
+ # useRoute Hook Migration
2
+
3
+ **Date:** 2026-01-14
4
+ **Affects:** `@clicktap/ui`, `apps/frontend`
5
+ **Breaking:** No (additive, but recommended migration)
6
+ **Classification:** IMPROVEMENT
7
+
8
+
9
+ ## Dependencies
10
+
11
+ *None*
12
+
13
+ ## Summary
14
+
15
+ Introduces a centralized route configuration system with the `useRoute` hook for generating URLs. Handles dynamic params, protected route redirects, and login-with-return patterns automatically.
16
+
17
+ ## New Files
18
+
19
+ - `libs/ui/src/hooks/useRoute/` - Hook and RouteProvider
20
+ - `apps/frontend/config/routes.ts` - Centralized route definitions
21
+
22
+ ## Provider Setup
23
+
24
+ Add `RouteProviderWithAuth` inside your `AuthProvider`:
25
+
26
+ ```tsx
27
+ import { RouteProvider } from '@clicktap/ui/hooks/useRoute';
28
+ import { useAuth } from '../state/auth/AuthProvider';
29
+
30
+ function RouteProviderWithAuth({ children }: { children: ReactNode }) {
31
+ const { isLoggedIn } = useAuth();
32
+ return <RouteProvider config={{ isLoggedIn }}>{children}</RouteProvider>;
33
+ }
34
+
35
+ // In provider tree:
36
+ <AuthProvider actor={authActor}>
37
+ <RouteProviderWithAuth>
38
+ {children}
39
+ </RouteProviderWithAuth>
40
+ </AuthProvider>
41
+ ```
42
+
43
+ ## Migration Patterns
44
+
45
+ ### Static Routes
46
+
47
+ ```tsx
48
+ // Before
49
+ <Link href="/cart">Cart</Link>
50
+
51
+ // After
52
+ import { useRoute } from '@clicktap/ui/hooks/useRoute';
53
+ import { Routes } from '../config/routes';
54
+
55
+ const cartUrl = useRoute(Routes.cart.index);
56
+ <Link href={cartUrl}>Cart</Link>
57
+ ```
58
+
59
+ ### Dynamic Routes
60
+
61
+ ```tsx
62
+ // Before
63
+ <Link href={`/account/orders/${orderId}`}>View Order</Link>
64
+
65
+ // After
66
+ const orderUrl = useRoute(Routes.account.orders.view, { orderId });
67
+ <Link href={orderUrl}>View Order</Link>
68
+ ```
69
+
70
+ ### Protected Routes
71
+
72
+ Protected routes automatically redirect through auth when logged out:
73
+
74
+ ```tsx
75
+ // Before
76
+ const ordersUrl = isLoggedIn
77
+ ? '/account/orders'
78
+ : `/account/authenticate?redirect=${encodeURIComponent('/account/orders')}`;
79
+
80
+ // After
81
+ const ordersUrl = useRoute(Routes.account.orders.index);
82
+ // Logged in: '/account/orders'
83
+ // Logged out: '/account/authenticate?redirect=%2Faccount%2Forders'
84
+ ```
85
+
86
+ ### Login with Return
87
+
88
+ ```tsx
89
+ // Before
90
+ const loginUrl = `/account/authenticate?redirect=${encodeURIComponent(pathname)}`;
91
+ const loginUrl = Routes.account.loginAndReturn(Routes.cart.index);
92
+
93
+ // After
94
+ const loginUrl = useRoute(Routes.account.authenticate, null, { returnHere: true });
95
+ ```
96
+
97
+ ### router.push
98
+
99
+ ```tsx
100
+ // Before
101
+ router.push('/checkout');
102
+ router.push(`/checkout/success/${orderId}`);
103
+
104
+ // After
105
+ const checkoutUrl = useRoute(Routes.checkout.index);
106
+ router.push(checkoutUrl);
107
+
108
+ const successUrl = useRoute(Routes.checkout.success, { orderId });
109
+ router.push(successUrl);
110
+ ```
111
+
112
+ ## Adding New Routes
113
+
114
+ Add to `apps/frontend/config/routes.ts`:
115
+
116
+ ```tsx
117
+ const RawRoutes = {
118
+ // Static route
119
+ newPage: {
120
+ path: '/new-page',
121
+ isProtected: false,
122
+ },
123
+
124
+ // Nested with dynamic param
125
+ feature: {
126
+ index: {
127
+ path: '/feature',
128
+ isProtected: false,
129
+ },
130
+ details: {
131
+ path: '/feature/:featureId',
132
+ isProtected: true,
133
+ },
134
+ },
135
+ } as const;
136
+ ```
137
+
138
+ ## Deprecated
139
+
140
+ - `Routes.account.loginAndReturn()` - Use `useRoute(route, null, { returnHere: true })`
141
+ - `isProtectedRoute(pathname)` - Use `route.isProtected` directly
142
+ - Direct `.path` access - Use `useRoute()` for all URL generation