@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.
- package/components/Accordion/Accordion.d.ts +1 -1
- package/components/Accordion/Accordion.js +1 -1
- package/components/Accordion/index.d.ts +2 -2
- package/components/Accordion/index.js +1 -1
- package/components/Badge/Badge.js +1 -1
- package/components/Badge/Badge.types.d.ts +1 -1
- package/components/Badge/index.js +1 -1
- package/components/Collection/CollectionFilters/CollectionFilters.js +1 -1
- package/components/Collection/CollectionFilters/index.js +1 -1
- package/components/Collection/index.js +1 -1
- package/components/Pagination/Pagination.d.ts +1 -1
- package/components/Pagination/Pagination.js +1 -1
- package/components/Pagination/index.js +1 -1
- package/components/Tooltip/Tooltip.d.ts +1 -1
- package/components/Tooltip/Tooltip.js +1 -1
- package/components/Tooltip/Tooltip.types.d.ts +1 -1
- package/components/Tooltip/index.js +1 -1
- package/docs/migrations/2026/01/2026-01-14-external-link-detection.md +290 -0
- package/docs/migrations/2026/01/2026-01-14-use-route-hook.md +142 -0
- package/docs/migrations/2026/01/2026-01-15-input-disable-skeleton.md +82 -0
- package/docs/migrations/2026/02/2026-02-16-env-utils-from-clicktap-ui.md +94 -0
- package/docs/migrations/2026/02/2026-02-17-select-popover-portal-container-restored.md +84 -0
- package/docs/migrations/2026/03/2026-03-24-image-dimmed-hidden-onready.md +108 -0
- package/docs/migrations/2026/03/2026-03-24-modal-overlay-backdrop-dismiss.md +81 -0
- package/docs/migrations/2026/04/2026-04-17-select-address-input-onchange-key-or-item.md +182 -0
- package/docs/migrations/2026/04/2026-04-24-image-placeholder-frontend-fallback.md +85 -0
- package/docs/migrations/2026/04/2026-04-30-prune-invalid-pdp-selections.md +166 -0
- package/docs/migrations/2026/04/2026-04-30-top-down-pdp-option-dependency.md +286 -0
- package/docs/migrations/2026/05/2026-05-01-build-pdp-url-util.md +141 -0
- package/docs/migrations/2026/05/2026-05-03-auth-aware-graphql-client.md +278 -0
- package/docs/migrations/2026/05/2026-05-20-framer-motion-11-idioms.md +391 -0
- package/docs/migrations/2026/05/2026-05-21-button-loading-content.md +265 -0
- package/docs/migrations/2026/05/2026-05-21-password-check-hide-on-empty.md +50 -0
- package/docs/migrations/2026/05/2026-05-29-password-check-requirements-variant.md +63 -0
- package/docs/migrations/2026/06/2026-06-17-select-standardization.md +286 -0
- package/docs/migrations/2026/06/2026-06-18-select-hiddenselect-setvalue-crash.md +75 -0
- package/docs/migrations/2026/07/2026-07-31-ui-exports-bundler-resolution.md +50 -0
- package/docs/migrations/2026/08/2026-08-02-nextui-to-heroui.md +65 -0
- package/docs/migrations/README.md +31 -0
- package/package.json +7 -5
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { AccordionProps } from '@
|
|
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"@
|
|
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 '@
|
|
3
|
-
export type { AccordionProps } from '@
|
|
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"@
|
|
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"@
|
|
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 '@
|
|
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"@
|
|
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"@
|
|
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"@
|
|
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"@
|
|
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 '@
|
|
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"@
|
|
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"@
|
|
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('@
|
|
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"@
|
|
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 '@
|
|
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"@
|
|
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
|