@clicktap/ui 0.41.1 → 0.42.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/components/Accordion/Accordion.js +1 -1
- package/components/Accordion.d.ts +1 -0
- package/components/Accordion.js +1 -0
- package/components/AssetRenderer/renderers/DownloadLink.d.ts +1 -1
- package/components/AssetRenderer/renderers.d.ts +1 -0
- package/components/AssetRenderer/renderers.js +1 -0
- package/components/AssetRenderer.d.ts +1 -0
- package/components/AssetRenderer.js +1 -0
- package/components/Avatar/Avatar.js +1 -1
- package/components/Avatar.d.ts +1 -0
- package/components/Avatar.js +1 -0
- package/components/Badge/Badge.js +1 -1
- package/components/Badge.d.ts +1 -0
- package/components/Badge.js +1 -0
- package/components/Breadcrumbs/BreadcrumbLink.js +1 -1
- package/components/Breadcrumbs.d.ts +1 -0
- package/components/Breadcrumbs.js +1 -0
- package/components/Button/Button.js +1 -1
- package/components/Button/ButtonLoadingContent.js +1 -1
- package/components/Button/index.js +1 -1
- package/components/Button.d.ts +1 -0
- package/components/Button.js +1 -0
- package/components/Card.d.ts +1 -0
- package/components/Card.js +1 -0
- package/components/Checkbox/Checkbox.js +1 -1
- package/components/Checkbox.d.ts +1 -0
- package/components/Checkbox.js +1 -0
- package/components/Collapsible.d.ts +1 -0
- package/components/Collapsible.js +1 -0
- package/components/Collection/AttributeDataProvider.d.ts +1 -0
- package/components/Collection/AttributeDataProvider.js +1 -0
- package/components/Collection/CollectionFilterChips/CollectionFilterChips.js +1 -1
- package/components/Collection/CollectionFilterChips/index.js +1 -1
- package/components/Collection/CollectionFilterChips.d.ts +1 -0
- package/components/Collection/CollectionFilterChips.js +1 -0
- package/components/Collection/CollectionFilters/CollectionFilters.js +1 -1
- package/components/Collection/CollectionFilters/index.js +1 -1
- package/components/Collection/CollectionFilters.d.ts +1 -0
- package/components/Collection/CollectionFilters.js +1 -0
- package/components/Collection/CollectionToolbar/CollectionToolbar.js +1 -1
- package/components/Collection/CollectionToolbar/index.js +1 -1
- package/components/Collection/CollectionToolbar.d.ts +1 -0
- package/components/Collection/CollectionToolbar.js +1 -0
- package/components/Collection/EmptyResults/EmptyResults.js +1 -1
- package/components/Collection/EmptyResults.d.ts +1 -0
- package/components/Collection/EmptyResults.js +1 -0
- package/components/Collection/FilterAccordionTitle/FilterAccordionTitle.js +1 -1
- package/components/Collection/FilterAccordionTitle.d.ts +1 -0
- package/components/Collection/FilterAccordionTitle.js +1 -0
- package/components/Collection/FilterRenderer/Renderer/Boolean.js +1 -1
- package/components/Collection/FilterRenderer/Renderer/Option.js +1 -1
- package/components/Collection/FilterRenderer/Renderer/Range.js +1 -1
- package/components/Collection/FilterRenderer/Renderer/Swatch/SwatchColor.js +1 -1
- package/components/Collection/FilterRenderer/Renderer/Swatch/SwatchImage.js +1 -1
- package/components/Collection/FilterRenderer/Renderer/Swatch/SwatchText.js +1 -1
- package/components/Collection/FilterRenderer/Renderer.d.ts +1 -0
- package/components/Collection/FilterRenderer/Renderer.js +1 -0
- package/components/Collection/FilterRenderer.d.ts +1 -0
- package/components/Collection/FilterRenderer.js +1 -0
- package/components/Collection/ProductCardSwatches/ProductCardSwatches.js +1 -1
- package/components/Collection/ProductCardSwatches.d.ts +1 -0
- package/components/Collection/ProductCardSwatches.js +1 -0
- package/components/Collection/ProductPrice.d.ts +1 -0
- package/components/Collection/ProductPrice.js +1 -0
- package/components/Collection/index.js +1 -1
- package/components/Collection.d.ts +1 -0
- package/components/Collection.js +1 -0
- package/components/Container.d.ts +1 -0
- package/components/Container.js +1 -0
- package/components/ContextMenu/ContextMenu.js +1 -1
- package/components/ContextMenu.d.ts +1 -0
- package/components/ContextMenu.js +1 -0
- package/components/CreditCardExpirationInput/CreditCardExpirationInput.js +1 -1
- package/components/CreditCardExpirationInput.d.ts +1 -0
- package/components/CreditCardExpirationInput.js +1 -0
- package/components/CreditCardInput/CreditCardInput.js +1 -1
- package/components/CreditCardInput.d.ts +1 -0
- package/components/CreditCardInput.js +1 -0
- package/components/DateInput/DateInput.js +1 -1
- package/components/DateInput.d.ts +1 -0
- package/components/DateInput.js +1 -0
- package/components/DateTimeFormat/DateTimeFormat.d.ts +1 -1
- package/components/DateTimeFormat.d.ts +1 -0
- package/components/DateTimeFormat.js +1 -0
- package/components/Dialog/Dialog.js +1 -1
- package/components/Dialog.d.ts +1 -0
- package/components/Dialog.js +1 -0
- package/components/DialogTrigger.d.ts +1 -0
- package/components/DialogTrigger.js +1 -0
- package/components/Divider.d.ts +1 -0
- package/components/Divider.js +1 -0
- package/components/DobInput/DobInput.js +1 -1
- package/components/DobInput.d.ts +1 -0
- package/components/DobInput.js +1 -0
- package/components/Drawer/Drawer.js +1 -1
- package/components/Drawer.d.ts +1 -0
- package/components/Drawer.js +1 -0
- package/components/FeatureGate/AccessDenied.d.ts +2 -2
- package/components/FeatureGate/AccessDenied.js +1 -1
- package/components/FeatureGate/FeatureGate.d.ts +1 -1
- package/components/FeatureGate/FeatureGate.js +1 -1
- package/components/FeatureGate/PermissionRequired.d.ts +1 -1
- package/components/FeatureGate/PermissionRequired.js +1 -1
- package/components/FeatureGate/UpgradePrompt.d.ts +1 -1
- package/components/FeatureGate/UpgradePrompt.js +1 -1
- package/components/Icon/NoEntryIcon.js +1 -1
- package/components/Icon/StarIcon.js +1 -1
- package/components/Icon.d.ts +1 -0
- package/components/Icon.js +1 -0
- package/components/Image/Image.d.ts +44 -3
- package/components/Image/Image.js +1 -1
- package/components/Image/ImageContext.d.ts +30 -0
- package/components/Image/ImageContext.js +1 -0
- package/components/Image/imageShared.d.ts +74 -0
- package/components/Image/imageShared.js +1 -0
- package/components/Image/index.d.ts +3 -1
- package/components/Image/index.js +1 -1
- package/components/Image.d.ts +1 -0
- package/components/Image.js +1 -0
- package/components/Input/Input.js +1 -1
- package/components/Input.d.ts +1 -0
- package/components/Input.js +1 -0
- package/components/Link/Link.js +1 -1
- package/components/Link.d.ts +1 -0
- package/components/Link.js +1 -0
- package/components/LinkAsButton/LinkAsButton.js +1 -1
- package/components/LinkAsButton/index.js +1 -1
- package/components/LinkAsButton.d.ts +1 -0
- package/components/LinkAsButton.js +1 -0
- package/components/Loader/CircularEasing.js +1 -1
- package/components/Loader/Pulse.js +1 -1
- package/components/Loader/index.js +1 -1
- package/components/Loader.d.ts +1 -0
- package/components/Loader.js +1 -0
- package/components/Menu.d.ts +1 -0
- package/components/Menu.js +1 -0
- package/components/Meter/Meter.js +1 -1
- package/components/Meter.d.ts +1 -0
- package/components/Meter.js +1 -0
- package/components/Modal.d.ts +1 -0
- package/components/Modal.js +1 -0
- package/components/ModalOverlay.d.ts +1 -0
- package/components/ModalOverlay.js +1 -0
- package/components/NumberFormat/NumberFormat.d.ts +1 -1
- package/components/NumberFormat.d.ts +1 -0
- package/components/NumberFormat.js +1 -0
- package/components/NumberInput/NumberInput.js +1 -1
- package/components/NumberInput.d.ts +1 -0
- package/components/NumberInput.js +1 -0
- package/components/NumberTicker.d.ts +1 -0
- package/components/NumberTicker.js +1 -0
- package/components/Pagination/Pagination.js +1 -1
- package/components/Pagination.d.ts +1 -0
- package/components/Pagination.js +1 -0
- package/components/PasswordCheck/PasswordCheck.js +1 -1
- package/components/PasswordCheck.d.ts +1 -0
- package/components/PasswordCheck.js +1 -0
- package/components/PhoneInput/PhoneInput.js +1 -1
- package/components/PhoneInput.d.ts +1 -0
- package/components/PhoneInput.js +1 -0
- package/components/PinInput/PinInput.js +1 -1
- package/components/PinInput.d.ts +1 -0
- package/components/PinInput.js +1 -0
- package/components/Progressbar/LinearProgressbar.js +1 -1
- package/components/Progressbar.d.ts +1 -0
- package/components/Progressbar.js +1 -0
- package/components/Radio/Radio.js +1 -1
- package/components/Radio.d.ts +1 -0
- package/components/Radio.js +1 -0
- package/components/RadioGroup/RadioGroup.js +1 -1
- package/components/RadioGroup.d.ts +1 -0
- package/components/RadioGroup.js +1 -0
- package/components/ResendCode.d.ts +1 -0
- package/components/ResendCode.js +1 -0
- package/components/Select/ButtonSelect.js +1 -1
- package/components/Select/ComboBoxSelect.js +1 -1
- package/components/Select/MultiButtonSelect.js +1 -1
- package/components/Select/Select.js +1 -1
- package/components/Select/index.js +1 -1
- package/components/Select/parts.js +1 -1
- package/components/Select.d.ts +1 -0
- package/components/Select.js +1 -0
- package/components/Skeleton.d.ts +1 -0
- package/components/Skeleton.js +1 -0
- package/components/Slider/Slider.js +1 -1
- package/components/Slider.d.ts +1 -0
- package/components/Slider.js +1 -0
- package/components/Switch/Switch.js +1 -1
- package/components/Switch.d.ts +1 -0
- package/components/Switch.js +1 -0
- package/components/Table.d.ts +1 -0
- package/components/Table.js +1 -0
- package/components/Tabs/Tab.js +1 -1
- package/components/Tabs/TabList.js +1 -1
- package/components/Tabs.d.ts +1 -0
- package/components/Tabs.js +1 -0
- package/components/Time.d.ts +1 -0
- package/components/Time.js +1 -0
- package/components/TimeInput/TimeInput.js +1 -1
- package/components/TimeInput.d.ts +1 -0
- package/components/TimeInput.js +1 -0
- package/components/Timezone.d.ts +1 -0
- package/components/Timezone.js +1 -0
- package/components/ToggleButton/ToggleButton.js +1 -1
- package/components/ToggleButton.d.ts +1 -0
- package/components/ToggleButton.js +1 -0
- package/components/Tooltip.d.ts +1 -0
- package/components/Tooltip.js +1 -0
- package/components/UploadImage/UploadImage.js +1 -1
- package/components/UploadImage/index.js +1 -1
- package/components/UploadImage.d.ts +1 -0
- package/components/UploadImage.js +1 -0
- package/docs/migrations/2026/04/2026-04-24-image-placeholder-frontend-fallback.md +3 -3
- package/docs/migrations/2026/04/2026-04-30-prune-invalid-pdp-selections.md +2 -2
- package/docs/migrations/2026/04/2026-04-30-top-down-pdp-option-dependency.md +2 -2
- package/docs/migrations/2026/08/2026-08-06-clicktap-ui-image-next-subpath.md +101 -0
- package/docs/migrations/2026/08/2026-08-06-clicktap-ui-loaders-drop-framer-motion.md +77 -0
- package/docs/migrations/2026/08/2026-08-06-clicktap-ui-semantic-tokens.md +106 -0
- package/docs/migrations/README.md +1 -0
- package/hooks/useCollectionState.d.ts +1 -0
- package/hooks/useCollectionState.js +1 -0
- package/hooks/useRoute/RouteContext.d.ts +8 -0
- package/hooks/useRoute/index.js +1 -1
- package/hooks/useRoute/useRoute.js +1 -1
- package/hooks/useRoute.d.ts +1 -0
- package/hooks/useRoute.js +1 -0
- package/next/Image.d.ts +11 -0
- package/next/Image.js +1 -0
- package/next/index.d.ts +8 -0
- package/next/index.js +1 -0
- package/package.json +62 -6
- package/theme.css +162 -0
- package/utils/collection.d.ts +1 -0
- package/utils/collection.js +1 -0
- package/utils/variantOptions.d.ts +1 -0
- package/utils/variantOptions.js +1 -0
- package/tailwind.config.js +0 -6
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './Switch/index';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './Switch/index.js';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './Table/index';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './Table/index.js';
|
package/components/Tabs/Tab.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
"use client";import{jsx as
|
|
1
|
+
"use client";import{jsx as d,jsxs as u,Fragment as c}from"react/jsx-runtime";import{useContext as b}from"react";import{Tab as f}from"react-aria-components";import{motion as m}from"framer-motion";import{cn as i}from"../../utils/cn.js";import{TabsOrientationContext as p}from"./Tabs.context.js";function x({orientation:o,variant:e="base",className:t,children:l,...r}){return d(f,{className:i("flex items-center relative z-10","text-sm cursor-pointer forced-color-adjust-none","transition-all duration-300 ease-in-out","justify-center","disabled:opacity-50","selected:outline-0 hover:outline-0 hover:text-fg-muted",e==="underline"&&o==="vertical"&&"justify-start","py-2 px-3",e==="underline"&&o==="horizontal"&&"p-3",e==="underline"&&o==="vertical"&&"py-3 pr-3 pl-0",(e==="underline"||e==="outline"||e==="enclosed")&&"text-slate-800",e==="solid"&&"text-fg-subtle",(e==="underline"||e==="outline")&&"selected:text-slate-800",(e==="solid"||e==="enclosed")&&"selected:text-slate-100",t),...r,children:l})}function w({variant:o="base",children:e,className:t,classNames:l,...r}){const s=b(p);return d(x,{orientation:s,variant:o,className:t,...r,children:n=>u(c,{children:[typeof e=="function"?e({defaultChildren:void 0,...n}):e,(n.isFocusVisible||n.isSelected)&&d(m.span,{className:i("absolute z-0",o==="solid"&&"inset-0 rounded-lg bg-slate-600 mix-blend-color",o==="outline"&&"inset-0 rounded-lg border-solid border-2 border-surface-inverse-hover",o==="underline"&&"rounded-lg bg-surface-inverse-hover mix-blend-color bottom-0",o==="underline"&&s==="horizontal"&&"left-0 w-full h-px",o==="underline"&&s==="vertical"&&"right-0 w-px h-full",o==="enclosed"&&"inset-0 border-solid border-1 border-surface-inverse-hover -mb-px border-b-0 rounded-t-lg rounded-b-none bg-white mix-blend-difference",o==="base"&&"hidden",l?.overflow),layoutId:o,transition:{type:"spring",bounce:.2,duration:.75}})]})})}export{x as BaseTab,w as Tab,w as default};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
"use client";import{jsx as r}from"react/jsx-runtime";import{useContext as n}from"react";import{TabList as f}from"react-aria-components";import{cn as s}from"../../utils/cn.js";import{BaseTab as m}from"./Tab.js";import{TabsOrientationContext as c}from"./Tabs.context.js";import"framer-motion";function T({items:t,children:i,variant:e="base",className:l,...
|
|
1
|
+
"use client";import{jsx as r}from"react/jsx-runtime";import{useContext as n}from"react";import{TabList as f}from"react-aria-components";import{cn as s}from"../../utils/cn.js";import{BaseTab as m}from"./Tab.js";import{TabsOrientationContext as c}from"./Tabs.context.js";import"framer-motion";function T({items:t,children:i,variant:e="base",className:l,...b}){const o=n(c);return r(f,{className:s("flex flex-row w-full flex-wrap gap-2 relative",o==="vertical"&&"orientation-vertical:max-w-max orientation-vertical:flex-col orientation-vertical:justify-start",[e==="solid"&&["bg-slate-200 rounded-xl p-1"],e==="outline"&&["border-solid border-2 border-border-subtle rounded-xl p-1"],e==="underline"&&o==="vertical"&&["before:block before:absolute before:inset-y-0 before:right-0 before:w-px before:bg-slate-200"],e==="underline"&&o==="horizontal"&&["before:block before:absolute before:inset-x-0 before:bottom-0 before:h-px before:bg-slate-200"]],l),items:t,...b,children:i||(a=>r(m,{orientation:o,children:a.title}))})}export{T as TabList,T as default};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './Tabs/index';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './Tabs/index.js';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './Time/index';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './Time/index.js';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
"use client";import{jsxs as
|
|
1
|
+
"use client";import{jsxs as s,jsx as r}from"react/jsx-runtime";import{TimeField as u,Label as f,DateInput as c,DateSegment as b,Text as x,FieldError as g}from"react-aria-components";import{cn as e}from"../../utils/cn.js";function v({label:a,description:i,errorMessage:o,className:l,classNames:t,...d}){return s(u,{className:e("text-fg",l),...d,children:[r(f,{className:e("flex text-fg-muted text-sm",t?.label),children:a}),r(c,{className:e("flex items-center","border-solid border border-border rounded-md","text-sm text-fg","h-10 px-1 py-0 m-0 w-full","bg-white","transition-all duration-200 ease-in-out","hover:border-border-emphasis","focus-within:outline-2 focus-within:outline focus-within:outline-border-subtle focus-within:border-border-emphasis","disabled:border-border-subtle disabled:bg-surface-subtle","invalid:border-danger-soft invalid:bg-danger-surface invalid:text-danger","invalid:hover:border-danger invalid:focus-within:border-danger invalid:focus-within:outline-danger-ring",t?.input),children:n=>r(b,{segment:n,className:e("p-1 tabular-nums text-end text-fg","invalid:text-danger-soft","disabled:cursor-default disabled:select-none disabled:text-fg-subtle","focus:text-fg focus:bg-slate-200 focus:outline-0 focus:rounded-md focus:caret-transparent focus:invalid:bg-danger-soft focus:invalid:text-white","type-literal:p-0.5","data-[placeholder]:text-fg-subtle data-[placeholder]:invalid:text-danger-soft data-[placeholder]:invalid:focus:text-white","aria-[readonly]:focus-visible:outline aria-[readonly]:focus-visible:outline-slate-500 aria-[readonly]:focus-visible:outline-1","aria-[readonly]:focus:outline aria-[readonly]:focus:outline-slate-500 aria-[readonly]:focus:bg-transparent aria-[readonly]:focus:outline-2",t?.segment)})}),i&&r(x,{className:e("flex text-fg-muted text-sm",t?.description),slot:"description",children:i}),r(g,{className:e("flex text-danger-soft text-sm",t?.error),children:o})]})}export{v as TimeInput,v as default};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './TimeInput/index';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './TimeInput/index.js';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './Timezone/index';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './Timezone/index.js';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
"use client";import{jsx as
|
|
1
|
+
"use client";import{jsx as a}from"react/jsx-runtime";import{ToggleButton as n}from"react-aria-components";import{cn as i}from"../../utils/cn.js";function g({children:d,size:e="md",variant:r="solid",className:t,...b}){return a(n,{className:s=>{const{isDisabled:o}=s;return i("flex items-center justify-center","font-semibold text-sm","rounded-md border-solid border","px-4","transition-all duration-200 ease-in-out","focus:outline-2 focus:outline focus:outline-border-subtle pressed:scale-95",{"h-8":e==="sm","h-10":e==="md","h-12":e==="lg"},[r==="ghost"&&["bg-transparent hover:bg-transparent focus:bg-transparent disabled:bg-transparent","border-transparent hover:border-transparent focus:border-transparent disabled:border-transparent","text-fg disabled:text-fg-subtle",o&&["disabled:text-fg","opacity-75"]],r==="outline"&&["bg-transparent hover:bg-transparent focus:bg-transparent disabled:bg-transparent","border-border hover:border-border-emphasis focus:border-border-emphasis disabled:border-border-subtle","text-fg disabled:text-fg-muted",o&&["disabled:border-border","disabled:text-fg","opacity-75"]],r==="solid"&&["bg-surface-inverse-hover hover:bg-surface-inverse focus:bg-surface-inverse disabled:bg-surface-inverse","border-surface-inverse-hover hover:border-fg focus:border-fg disabled:border-border-subtle","text-white disabled:text-fg-subtle",o&&["disabled:border-fg","disabled:text-white","opacity-75"]]],typeof t=="function"?t(s):t)},...b,children:d})}export{g as ToggleButton,g as default};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './ToggleButton/index';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './ToggleButton/index.js';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './Tooltip/index';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './Tooltip/index.js';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
"use client";import{jsxs as l,Fragment as
|
|
1
|
+
"use client";import{jsxs as l,Fragment as u,jsx as e}from"react/jsx-runtime";import{forwardRef as y,useState as v}from"react";import{motion as j}from"framer-motion";import{FieldError as V}from"react-aria-components";import{cn as r}from"../../utils/cn.js";import _ from"../../hooks/useIsClient.js";import{Skeleton as p}from"../Skeleton/Skeleton.js";function k({hasTitle:a,className:s}){return l("div",{className:"w-full h-full flex flex-col gap-4",children:[a&&e(p,{className:"w-1/2 h-8 mx-auto rounded-md z-20"}),e(p,{className:r("w-full h-56 rounded-md z-20 relative",s)})]})}const H=y(({title:a,description:s="Preview will display here.",fileExtension:m,actionTitle:g="select file",variant:i,errorMessage:h,defaultImagePath:n,classNames:f,...c},b)=>{const x=_(),[t,w]=v({src:null,alt:null}),C=!!h,N=m?m.split(",").map(o=>`image/${o.trim()}`).join(", "):"image/*";return l("div",{className:r("w-full h-full","flex flex-col gap-4"),children:[x?l(u,{children:[a&&e("h6",{className:r("text-center","text-2xl","m-0 mb-4"),children:a}),l("div",{className:r("w-full h-full","flex flex-col justify-between gap-5 items-center",["bg-surface-subtle",i==="base"&&"bg-transparent"],"rounded-md",[i==="base"&&"rounded-none"],["p-6 lg:p-7",i==="base"&&"p-0 lg:p-0"]),children:[l("label",{htmlFor:`${c?.name}-upload-file`,"aria-label":"image upload",onChange:o=>{const{target:{files:d}}=o;d&&w({src:URL.createObjectURL(d[0]),alt:d[0].name})},className:r("inline-flex justify-center items-center","uppercase text-base border-border-subtle","bg-transparent","border-1 border-solid","px-4 py-0","rounded-2xl","cursor-pointer","w-full h-9",["max-w-48",i==="base"&&"max-w-none"],"my-0 mx-auto","has-[:disabled]:bg-transparent has-[:disabled]:text-fg-strong","hover:bg-slate-100s hover:text-black",f?.label),children:[g,e("input",{id:`${c?.name}-upload-file`,accept:N,type:"file",hidden:!0,...c,ref:b,className:"hidden"})]}),n&&!t.src?e("div",{className:"flex justify-center items-center w-60",children:e("a",{href:n,target:"_blank",rel:"noreferrer",children:e("img",{src:n,alt:"",className:"w-full h-auto"})})}):l(u,{children:[e(j.div,{initial:{opacity:0},animate:{opacity:1},transition:{type:"spring",duration:3,bounce:0},className:"flex justify-center items-center w-52 h-auto",children:t.src?e("img",{src:t.src,height:96,width:200,alt:t.alt||"",className:"max-h-full object-contain"}):l("svg",{xmlns:"http://www.w3.org/2000/svg",width:"31",height:"55",viewBox:"0 0 31 55",fill:"none",children:[e("g",{clipPath:"url(#clip0_15607_32844)",children:e("path",{d:"M15.9521 0.721924C24.2431 0.721924 30.9521 7.36392 30.9521 15.5719V43.9219C30.9521 49.8889 26.0703 54.7219 20.0431 54.7219C14.0158 54.7219 9.13397 49.8889 9.13397 43.9219V20.9719C9.13397 17.2459 12.1885 14.2219 15.9521 14.2219C19.7158 14.2219 22.7703 17.2459 22.7703 20.9719V41.2219H17.3158V20.7289C17.3158 19.2439 14.5885 19.2439 14.5885 20.7289V43.9219C14.5885 46.8919 17.0431 49.3219 20.0431 49.3219C23.0431 49.3219 25.4976 46.8919 25.4976 43.9219V15.5719C25.4976 10.3609 21.2158 6.12192 15.9521 6.12192C10.6885 6.12192 6.40669 10.3609 6.40669 15.5719V41.2219H0.952148V15.5719C0.952148 7.36392 7.66124 0.721924 15.9521 0.721924Z",fill:"#646464"})}),e("defs",{children:e("clipPath",{id:"clip0_15607_32844",children:e("rect",{width:"30",height:"54",fill:"white",transform:"translate(0.952148 0.721924)"})})})]})},t.src),e("p",{className:"m-0 text-fg-strong text-center",children:t.alt||s})]})]})]}):e(k,{hasTitle:!!a,className:f?.skeleton}),C&&e(V,{className:r("-mt-2","self-start"),children:h})]})});export{H as UploadImage,H as default};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{UploadImage as
|
|
1
|
+
import{UploadImage as f}from"./UploadImage.js";import"react/jsx-runtime";import"react";import"framer-motion";import"react-aria-components";import"../../utils/cn.js";import"../../hooks/useIsClient.js";import"../Skeleton/Skeleton.js";export{f as UploadImage};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './UploadImage/index';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './UploadImage/index.js';
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
|
|
8
8
|
## Dependencies
|
|
9
9
|
|
|
10
|
-
- See [frontend counterpart](2026-04-24-image-placeholder-frontend-fallback.md) ↔ for the consuming-app changes.
|
|
10
|
+
- See [frontend counterpart](../../../../../../apps/frontend/docs/migrations/2026/04/2026-04-24-image-placeholder-frontend-fallback.md) ↔ for the consuming-app changes.
|
|
11
11
|
|
|
12
12
|
## Summary
|
|
13
13
|
|
|
@@ -60,7 +60,7 @@ Works equally well with raw `<img>`:
|
|
|
60
60
|
<img src={product.image ?? PLACEHOLDER_IMAGE} alt="" />
|
|
61
61
|
```
|
|
62
62
|
|
|
63
|
-
For the PDP/PLP migration, see the [frontend counterpart guide](2026-04-24-image-placeholder-frontend-fallback.md) — it covers the full cascade decision (variant → placeholder, no parent fallback) and the dead `no_selection` checks to remove.
|
|
63
|
+
For the PDP/PLP migration, see the [frontend counterpart guide](../../../../../../apps/frontend/docs/migrations/2026/04/2026-04-24-image-placeholder-frontend-fallback.md) — it covers the full cascade decision (variant → placeholder, no parent fallback) and the dead `no_selection` checks to remove.
|
|
64
64
|
|
|
65
65
|
## Files Changed
|
|
66
66
|
|
|
@@ -74,7 +74,7 @@ For the PDP/PLP migration, see the [frontend counterpart guide](2026-04-24-image
|
|
|
74
74
|
|
|
75
75
|
- [ ] `@clicktap/ui >= 0.27.0` installed in consumers.
|
|
76
76
|
- [ ] No need to copy any SVG into the consuming app's `public/` folder.
|
|
77
|
-
- [ ] Apply the [frontend counterpart guide](2026-04-24-image-placeholder-frontend-fallback.md) for the consuming-app changes.
|
|
77
|
+
- [ ] Apply the [frontend counterpart guide](../../../../../../apps/frontend/docs/migrations/2026/04/2026-04-24-image-placeholder-frontend-fallback.md) for the consuming-app changes.
|
|
78
78
|
|
|
79
79
|
## Why a data URL and not a bundled file?
|
|
80
80
|
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
## Dependencies
|
|
9
9
|
|
|
10
10
|
- Builds on [`2026-04-30-top-down-pdp-option-dependency.md`](./2026-04-30-top-down-pdp-option-dependency.md). That migration introduced top-down option filtering with a blanket "clear every downstream selection on any upstream change". This migration replaces the recommended consumer pattern with a more conservative prune that preserves still-valid downstream picks.
|
|
11
|
-
- Frontend counterpart: [`apps/frontend/.../2026-04-30-prune-invalid-pdp-selections.md`](2026-04-30-prune-invalid-pdp-selections.md) ↔.
|
|
11
|
+
- Frontend counterpart: [`apps/frontend/.../2026-04-30-prune-invalid-pdp-selections.md`](../../../../../../apps/frontend/docs/migrations/2026/04/2026-04-30-prune-invalid-pdp-selections.md) ↔.
|
|
12
12
|
|
|
13
13
|
## Summary
|
|
14
14
|
|
|
@@ -162,5 +162,5 @@ Replace the manual delete loop with a single `pruneInvalidSelections` call (see
|
|
|
162
162
|
|
|
163
163
|
## References
|
|
164
164
|
|
|
165
|
-
- Frontend counterpart migration: [`apps/frontend/.../2026-04-30-prune-invalid-pdp-selections.md`](2026-04-30-prune-invalid-pdp-selections.md) ↔
|
|
165
|
+
- Frontend counterpart migration: [`apps/frontend/.../2026-04-30-prune-invalid-pdp-selections.md`](../../../../../../apps/frontend/docs/migrations/2026/04/2026-04-30-prune-invalid-pdp-selections.md) ↔
|
|
166
166
|
- Previous top-down migration this builds on: [`2026-04-30-top-down-pdp-option-dependency.md`](./2026-04-30-top-down-pdp-option-dependency.md)
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
|
|
8
8
|
## Dependencies
|
|
9
9
|
|
|
10
|
-
- Builds on the per-axis selection model used in [`apps/frontend/.../2026-04-24-pdp-configurable-selector-ux.md`](../../../../../../apps/frontend/docs/migrations/2026/04/2026-04-24-pdp-configurable-selector-ux.md). The previous symmetric filter lived in the frontend app; that logic now lives here in generic, reusable form. See [`apps/frontend/.../2026-04-30-top-down-pdp-option-dependency.md`](2026-04-30-top-down-pdp-option-dependency.md) ↔ for the frontend-side migration steps that adopt the new utilities and switch to top-down semantics.
|
|
10
|
+
- Builds on the per-axis selection model used in [`apps/frontend/.../2026-04-24-pdp-configurable-selector-ux.md`](../../../../../../apps/frontend/docs/migrations/2026/04/2026-04-24-pdp-configurable-selector-ux.md). The previous symmetric filter lived in the frontend app; that logic now lives here in generic, reusable form. See [`apps/frontend/.../2026-04-30-top-down-pdp-option-dependency.md`](../../../../../../apps/frontend/docs/migrations/2026/04/2026-04-30-top-down-pdp-option-dependency.md) ↔ for the frontend-side migration steps that adopt the new utilities and switch to top-down semantics.
|
|
11
11
|
|
|
12
12
|
## Summary
|
|
13
13
|
|
|
@@ -282,5 +282,5 @@ Skip this and you'll occasionally see the matched variant query against an impos
|
|
|
282
282
|
|
|
283
283
|
## References
|
|
284
284
|
|
|
285
|
-
- Frontend counterpart migration: [`apps/frontend/.../2026-04-30-top-down-pdp-option-dependency.md`](2026-04-30-top-down-pdp-option-dependency.md) ↔
|
|
285
|
+
- Frontend counterpart migration: [`apps/frontend/.../2026-04-30-top-down-pdp-option-dependency.md`](../../../../../../apps/frontend/docs/migrations/2026/04/2026-04-30-top-down-pdp-option-dependency.md) ↔
|
|
286
286
|
- Prior symmetric-filter implementation it replaces: [`apps/frontend/.../2026-04-24-pdp-configurable-selector-ux.md`](../../../../../../apps/frontend/docs/migrations/2026/04/2026-04-24-pdp-configurable-selector-ux.md)
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
# `Image` is framework-free; Next's optimizer moves to `@clicktap/ui/next`
|
|
2
|
+
|
|
3
|
+
**Date:** 2026-08-06
|
|
4
|
+
**Ticket:** CTAP-1125 (epic CTAP-1122)
|
|
5
|
+
**Affects:** Any consumer importing `@clicktap/ui/components/Image` or `@clicktap/ui/components/UploadImage`. Consumer counterpart: [`apps/frontend/docs/migrations/2026/08/2026-08-06-image-from-clicktap-ui-next.md`](../../../../../../apps/frontend/docs/migrations/2026/08/2026-08-06-image-from-clicktap-ui-next.md)
|
|
6
|
+
**Requires:** `@clicktap/ui` at the release bundling CTAP-1125. Next consumers additionally need `next >= 14` installed — it is now an **optional peer**, so your package manager will not add it for you.
|
|
7
|
+
**Classification:** IMPROVEMENT
|
|
8
|
+
**Breaking:** Yes, for Next consumers — one import path per call site. See "If you are a Next app" below.
|
|
9
|
+
|
|
10
|
+
## What changed and why
|
|
11
|
+
|
|
12
|
+
`next` was in `dependencies`, so **every** consumer installed Next to use **any**
|
|
13
|
+
component. That is the concrete blocker behind CTAP-1122: a Capacitor app wanted
|
|
14
|
+
one `PinInput`, could not take the package, and reimplemented it by reading the
|
|
15
|
+
built output.
|
|
16
|
+
|
|
17
|
+
- `@clicktap/ui/components/Image` now renders a plain `<img>` and imports nothing
|
|
18
|
+
from Next. Its whole transitive closure is 5 files importing only `react`.
|
|
19
|
+
- `@clicktap/ui/next/Image` is the previous `NextImage`-backed component, moved
|
|
20
|
+
verbatim — error boundary, failed-render cache, `onError` debounce and all.
|
|
21
|
+
- `next` moved from `dependencies` to an **optional peer**. A consumer that never
|
|
22
|
+
imports `@clicktap/ui/next` gets zero Next in its tree.
|
|
23
|
+
- `UploadImage` no longer touches Next at all (it used it for a fixed 96×200
|
|
24
|
+
preview and already rendered a plain `<img>` beside it).
|
|
25
|
+
|
|
26
|
+
## Read this before assuming you can drop Next
|
|
27
|
+
|
|
28
|
+
**Next's optimizer is a server capability, not markup.** It resizes on demand,
|
|
29
|
+
converts to WebP/AVIF, and caches. The base component cannot reproduce that — it
|
|
30
|
+
can only emit the right `srcset`, and it cannot invent variants that do not
|
|
31
|
+
exist.
|
|
32
|
+
|
|
33
|
+
So the question is not "Next or not", it is **what generates your variants**:
|
|
34
|
+
|
|
35
|
+
| Your images | What to do |
|
|
36
|
+
|---|---|
|
|
37
|
+
| Served from a **static store** with no resizing in front (e.g. `/assets/*`) | Use `@clicktap/ui/next`. Next's optimizer is the only thing producing variants |
|
|
38
|
+
| Behind a **resizing CDN** (Cloudinary, imgix, URL-param resizing) | Use the base component with a `loader`. Fewer hops than proxying through Next |
|
|
39
|
+
| **Bundled** with the app (local files the bundler sees) | Use the base component with `srcSet` from a build-time pipeline |
|
|
40
|
+
| No optimization available | Use the base component. Single-`src` `<img>` — correct, just unoptimized |
|
|
41
|
+
|
|
42
|
+
## If you are a Next app
|
|
43
|
+
|
|
44
|
+
One line per call site, and behaviour is identical:
|
|
45
|
+
|
|
46
|
+
```diff
|
|
47
|
+
- import { Image } from '@clicktap/ui/components/Image';
|
|
48
|
+
+ import { Image } from '@clicktap/ui/next/Image';
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Add `next` to your own `dependencies` if it is not already there (it will be).
|
|
52
|
+
|
|
53
|
+
**Do this in the same change as the upgrade.** The base component renders a
|
|
54
|
+
correct `<img>`, so leaving a call site behind compiles, passes tests, and
|
|
55
|
+
silently serves full-size images — a perf regression with no failing signal.
|
|
56
|
+
|
|
57
|
+
## If you are not a Next app
|
|
58
|
+
|
|
59
|
+
Import from `@clicktap/ui/components/Image` and supply variants one of two ways.
|
|
60
|
+
|
|
61
|
+
**Build-time (bundled images).** Your bundler already resolves the import, so
|
|
62
|
+
directives on the specifier drive it — no code scan, no maintained map. With
|
|
63
|
+
Vite:
|
|
64
|
+
|
|
65
|
+
```ts
|
|
66
|
+
imagetools({ defaultDirectives: () => new URLSearchParams('w=640;1200;1920&format=webp&as=srcset') })
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
```tsx
|
|
70
|
+
import hero from './hero.jpg';
|
|
71
|
+
<Image src={hero.src} srcSet={hero.srcset} sizes="100vw" />
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
**Runtime (remote URLs).** Supply a `loader` once, the same way `RouteProvider`
|
|
75
|
+
takes `pathname`:
|
|
76
|
+
|
|
77
|
+
```tsx
|
|
78
|
+
<ImageProvider config={{ loader: ({ src, width, quality }) => `${src}?w=${width}&q=${quality ?? 75}` }}>
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
`srcSet` wins over the loader — the bundler produced those exact files, so a
|
|
82
|
+
loader guessing URLs for the same image would be redundant and wrong.
|
|
83
|
+
|
|
84
|
+
## Prop contract on the base component
|
|
85
|
+
|
|
86
|
+
| Prop | Behaviour |
|
|
87
|
+
|---|---|
|
|
88
|
+
| `srcSet` | **New.** Build-time variants; takes precedence over the loader |
|
|
89
|
+
| `sizes` | Live whenever a `srcSet` exists (supplied or loader-generated); ignored otherwise, since it only means anything alongside a `srcset` |
|
|
90
|
+
| `quality` | Passed to the loader |
|
|
91
|
+
| `priority` | Honoured as `loading="eager"` + `fetchPriority="high"`. **No `<link rel="preload">`** — that is Next head management and only happens in `@clicktap/ui/next` |
|
|
92
|
+
| `fill` | Honoured via absolute-fill style |
|
|
93
|
+
| `dimmed` / `hidden` / `onReady` | Unchanged |
|
|
94
|
+
| `placeholder` / `blurDataURL` / Next's `loader` type | `@clicktap/ui/next` only |
|
|
95
|
+
|
|
96
|
+
## Verification
|
|
97
|
+
|
|
98
|
+
1. `grep -r "@clicktap/ui/components/Image"` — every remaining hit in a Next app
|
|
99
|
+
is a call site now serving unoptimized images.
|
|
100
|
+
2. In a Next app, confirm images still request `/_next/image?url=…`.
|
|
101
|
+
3. In a non-Next app, confirm no `next` resolves: `npm ls next` should be empty.
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# `Button` and the loaders no longer need `framer-motion` (CTAP-1156)
|
|
2
|
+
|
|
3
|
+
**Date:** 2026-08-06
|
|
4
|
+
**Affects:** `@clicktap/ui` — `Button`, `ButtonLoadingContent`, `Loader/CircularEasing`, `Loader/Pulse`
|
|
5
|
+
**Breaking:** No API change. **One requirement to be aware of** — see below.
|
|
6
|
+
**Requires:** `@import '@clicktap/ui/theme.css'` in your CSS entry
|
|
7
|
+
([tokens guide](./2026-08-06-clicktap-ui-semantic-tokens.md))
|
|
8
|
+
**Classification:** IMPROVEMENT
|
|
9
|
+
|
|
10
|
+
## What changed
|
|
11
|
+
|
|
12
|
+
`Button` used to require the optional `framer-motion` peer. Not for anything Button
|
|
13
|
+
does — it arrived through `ButtonLoadingContent` → `Loader/CircularEasing`, whose
|
|
14
|
+
spinner used framer-motion for two decorative infinite animations. That made the
|
|
15
|
+
package's **most likely first import** drag in a motion runtime.
|
|
16
|
+
|
|
17
|
+
Those animations are now CSS keyframes shipped in `theme.css`. `Loader/Pulse` moved
|
|
18
|
+
the same way, so the whole `Loader` subpath is motion-runtime-free.
|
|
19
|
+
|
|
20
|
+
## What you need to do
|
|
21
|
+
|
|
22
|
+
**Nothing, if you already import `@clicktap/ui/theme.css`** — which the tokens guide
|
|
23
|
+
already makes mandatory. No API changed, no props changed, and the animations look
|
|
24
|
+
identical: the durations and easings are the same values the framer-motion
|
|
25
|
+
transitions used (`ease-in-out` is the same cubic-bezier framer calls `easeInOut`).
|
|
26
|
+
|
|
27
|
+
The one thing to know: **`theme.css` now carries animation as well as colour.** If
|
|
28
|
+
you skip it, you get a *static* spinner on top of unstyled components. That is not a
|
|
29
|
+
new import — it is a new reason the existing one matters.
|
|
30
|
+
|
|
31
|
+
If you install `@clicktap/ui` fresh and only need `Button`, you can now drop
|
|
32
|
+
`framer-motion` from your dependencies entirely — unless you also use `Drawer`,
|
|
33
|
+
`Dialog`, `Tabs`, `ContextMenu` or `Badge`, which still need it.
|
|
34
|
+
|
|
35
|
+
## Why this was worth doing
|
|
36
|
+
|
|
37
|
+
The tiering in the README is enforced by `nx run ui:verify-minimal-install`, which
|
|
38
|
+
installs **only** the required peers into a temp dir outside the repo and bundles each
|
|
39
|
+
subpath. Before this change `Button` sat in the *gated* list with a comment admitting
|
|
40
|
+
the tiering was awkward precisely because of it. It now sits in the core list, with
|
|
41
|
+
the check proving it:
|
|
42
|
+
|
|
43
|
+
```
|
|
44
|
+
Core subpaths — must work with only: react@18 react-dom@18 react-aria-components
|
|
45
|
+
ok @clicktap/ui/components/Button resolves
|
|
46
|
+
ok @clicktap/ui/components/Loader resolves
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
A side benefit: framer-motion 11.x's complex-value parser could crash inside
|
|
50
|
+
`mixObject` when an ancestor `AnimatePresence` remeasured mid-flow, which is why the
|
|
51
|
+
old components carefully animated numeric primitives instead of transform strings.
|
|
52
|
+
CSS keyframes have no equivalent failure mode, so those workarounds are gone.
|
|
53
|
+
|
|
54
|
+
## If you are overriding the animations
|
|
55
|
+
|
|
56
|
+
Three keyframes and three animation tokens are exposed, so you can retime or replace
|
|
57
|
+
them without patching components:
|
|
58
|
+
|
|
59
|
+
| Token | Default |
|
|
60
|
+
|---|---|
|
|
61
|
+
| `--animate-ct-loader-spin` | `ct-loader-spin 2s linear infinite` |
|
|
62
|
+
| `--animate-ct-loader-dash` | `ct-loader-dash 1.5s ease-in-out infinite` |
|
|
63
|
+
| `--animate-ct-loader-pulse` | `ct-loader-pulse 0.75s ease-in-out infinite` |
|
|
64
|
+
|
|
65
|
+
Override like any other token:
|
|
66
|
+
|
|
67
|
+
```css
|
|
68
|
+
:root {
|
|
69
|
+
--animate-ct-loader-spin: ct-loader-spin 4s linear infinite;
|
|
70
|
+
}
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## Related
|
|
74
|
+
|
|
75
|
+
- **CTAP-1124** — the subpath entry points and optional-peer tiering this feeds into.
|
|
76
|
+
- **CTAP-1123** — the semantic tokens that made `theme.css` a required import.
|
|
77
|
+
- **CTAP-1122** — the epic making `@clicktap/ui` adoptable outside clicktap.
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
# `@clicktap/ui` semantic design tokens — add one CSS import (CTAP-1123)
|
|
2
|
+
|
|
3
|
+
**Date:** 2026-08-06
|
|
4
|
+
**Affects:** `@clicktap/ui`, every app that renders its components
|
|
5
|
+
**Breaking:** **Yes — one required import.** Components render *unstyled* without it.
|
|
6
|
+
**Requires:** Tailwind v4 (`@import 'tailwindcss'` in your CSS entry). See
|
|
7
|
+
[the v4 upgrade guide](../../../../../../apps/frontend/docs/migrations/2026/07/2026-07-31-tailwind-3-to-4.md).
|
|
8
|
+
**Classification:** COMPATIBILITY
|
|
9
|
+
|
|
10
|
+
## Do this
|
|
11
|
+
|
|
12
|
+
Add one line to your CSS entry, directly after the Tailwind import:
|
|
13
|
+
|
|
14
|
+
```css
|
|
15
|
+
@import 'tailwindcss';
|
|
16
|
+
@import '@clicktap/ui/theme.css'; /* <- add this */
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
That is the entire migration. There is no Tailwind config change, no preset, and no
|
|
20
|
+
`@config` migration required — `theme.css` uses v4's `@theme`, which both defines the
|
|
21
|
+
variables *and* registers the utilities that read them.
|
|
22
|
+
|
|
23
|
+
**It must sit before any non-`@import` rule.** CSS requires `@import` to precede all other
|
|
24
|
+
rules, so if you place it after (say) a `@config` line it is dropped **silently** — no
|
|
25
|
+
error, no variables, no utilities, and a build that passes. If your components look
|
|
26
|
+
unstyled after following this guide, check the ordering first.
|
|
27
|
+
|
|
28
|
+
## Why it is breaking
|
|
29
|
+
|
|
30
|
+
Components used to hardcode literal palette classes (`text-slate-900`, `border-slate-300`).
|
|
31
|
+
Those worked in any consumer only because slate and red are Tailwind **defaults** — every
|
|
32
|
+
config has them. They now use token classes (`text-fg`, `border-border-subtle`), which have
|
|
33
|
+
no values until this file is imported, so a component that skips the import renders with no
|
|
34
|
+
colour at all.
|
|
35
|
+
|
|
36
|
+
`@theme` composes with a JS config, so this works whether you are on `@config`, CSS-first,
|
|
37
|
+
or mid-migration between them.
|
|
38
|
+
|
|
39
|
+
## What you get
|
|
40
|
+
|
|
41
|
+
Re-theming is now a runtime override of a token — no rebuild, no config surgery — under a
|
|
42
|
+
class, an attribute, or a media query:
|
|
43
|
+
|
|
44
|
+
```css
|
|
45
|
+
:root[data-theme='dark'] {
|
|
46
|
+
--color-fg: oklch(98% 0 0);
|
|
47
|
+
--color-surface-subtle: oklch(25% 0.02 260);
|
|
48
|
+
}
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Previously a dark-themed app got a white box with dark text and could only fix it by
|
|
52
|
+
overriding every component state individually.
|
|
53
|
+
|
|
54
|
+
### The tokens
|
|
55
|
+
|
|
56
|
+
| Group | Tokens |
|
|
57
|
+
|---|---|
|
|
58
|
+
| Foreground | `fg-strong`, `fg`, `fg-muted`, `fg-subtle`, `fg-inverse` |
|
|
59
|
+
| Surface | `surface`, `surface-subtle`, `surface-inverse`, `surface-inverse-hover` |
|
|
60
|
+
| Border | `border-subtle`, `border`, `border-emphasis` |
|
|
61
|
+
| Danger | `danger`, `danger-soft`, `danger-surface`, `danger-ring` |
|
|
62
|
+
| Status | `success`, `warning`, `info` |
|
|
63
|
+
|
|
64
|
+
Each is a Tailwind colour token, so it composes with every colour utility: `text-fg`,
|
|
65
|
+
`bg-surface-subtle`, `border-border-subtle`, `outline-border-subtle`, `ring-danger-ring`.
|
|
66
|
+
|
|
67
|
+
**On `border-border-subtle`.** v4 has a single `--color-*` namespace feeding every colour
|
|
68
|
+
utility, so a token named `border-subtle` necessarily yields `border-border-subtle` —
|
|
69
|
+
Tailwind prefixes the utility itself. The doubling is real, it is the ecosystem convention
|
|
70
|
+
(shadcn/ui ships `border-border`, `bg-background`), and the same token reads correctly as
|
|
71
|
+
`outline-border-subtle` / `ring-border-subtle`.
|
|
72
|
+
|
|
73
|
+
## Nothing should look different
|
|
74
|
+
|
|
75
|
+
Every token's default is the **exact** palette value the components used before — verified
|
|
76
|
+
by asserting each one byte-for-byte against `tailwindcss/theme.css`. Adopting tokens is not
|
|
77
|
+
a restyle; if anything changes visually, that is a bug, so report it rather than working
|
|
78
|
+
around it.
|
|
79
|
+
|
|
80
|
+
## Two gotchas if you define your own tokens
|
|
81
|
+
|
|
82
|
+
Both cost real debugging time on this change:
|
|
83
|
+
|
|
84
|
+
1. **Plain `@theme` prunes tokens no utility references.** A token you define but never use
|
|
85
|
+
in a class simply will not exist at runtime, so hand-written CSS doing
|
|
86
|
+
`color: var(--color-brand)` gets nothing. Use `@theme static` for a token set others
|
|
87
|
+
consume. (`@clicktap/ui/theme.css` already does.)
|
|
88
|
+
2. **Do not define an alias by reference.** `--color-fill-subtle: var(--color-border-subtle)`
|
|
89
|
+
resolves at `:root`, so a later `.dark { --color-border-subtle: … }` override does **not**
|
|
90
|
+
propagate to the alias — dark mode silently half-applies. Either give the alias its own
|
|
91
|
+
literal value or do not create it.
|
|
92
|
+
|
|
93
|
+
## Known gaps
|
|
94
|
+
|
|
95
|
+
About 81 palette literals remain in lower-traffic components (`text-slate-600`,
|
|
96
|
+
`bg-slate-200`, `outline-slate-500` and similar). They are **not** a theming dead-end —
|
|
97
|
+
Tailwind compiles them to `var(--color-slate-600)` and emits the whole ramp at `:root`, so
|
|
98
|
+
you can still override them. They just widen your override surface beyond the token list
|
|
99
|
+
above. They were left literal deliberately: the values are a drifted ramp (eight distinct
|
|
100
|
+
shades for text alone), and consolidating them is a visual change that needs its own review
|
|
101
|
+
rather than being smuggled into a no-op refactor.
|
|
102
|
+
|
|
103
|
+
## Related
|
|
104
|
+
|
|
105
|
+
- **CTAP-1157** — the Tailwind v4 upgrade this depends on.
|
|
106
|
+
- **CTAP-1122** — the epic making `@clicktap/ui` adoptable outside clicktap.
|
|
@@ -29,5 +29,6 @@ For migrations affecting the frontend app, see [frontend migrations](../../../..
|
|
|
29
29
|
| 2026-06-18 | [Select (searchable): fix "setValue is not a function" crash on browser autofill](./2026/06/2026-06-18-select-hiddenselect-setvalue-crash.md) | No | Select standardization |
|
|
30
30
|
| 2026-08-04 | [`ResendCode` control + `useCooldown` hook](./2026/08/2026-08-04-resend-code-control.md) ↔ | No | OTP resend timer (CTAP-1114) |
|
|
31
31
|
| 2026-08-04 | [Password reveal is press-and-hold, not a toggle](./2026/08/2026-08-04-password-reveal-press-and-hold.md) | Behavioural | *None* (CTAP-1128) |
|
|
32
|
+
| 2026-08-06 | [`Image` is framework-free; Next's optimizer moves to `@clicktap/ui/next`](./2026/08/2026-08-06-clicktap-ui-image-next-subpath.md) ↔ | Yes (Next consumers) | *None* |
|
|
32
33
|
|
|
33
34
|
↔ = cross-app migration (has counterpart in frontend app)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './useCollectionState/index';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './useCollectionState/index.js';
|
|
@@ -2,6 +2,14 @@ import { ReactNode } from 'react';
|
|
|
2
2
|
export type RouteProviderConfig = {
|
|
3
3
|
/** Whether the user is currently logged in */
|
|
4
4
|
isLoggedIn: boolean;
|
|
5
|
+
/**
|
|
6
|
+
* Current pathname, supplied reactively by the consumer's router (e.g. Next's
|
|
7
|
+
* `usePathname()`). Used only by `useRoute(..., { returnHere: true })`. Keeping
|
|
8
|
+
* it in config — rather than importing `next/navigation` inside the hook — is
|
|
9
|
+
* what lets `@clicktap/ui` stay framework-agnostic (CTAP-1125). Omitted → the
|
|
10
|
+
* `returnHere` redirect falls back to `/`.
|
|
11
|
+
*/
|
|
12
|
+
pathname?: string;
|
|
5
13
|
};
|
|
6
14
|
type RouteProviderProps = {
|
|
7
15
|
children: ReactNode;
|
package/hooks/useRoute/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{buildPath as
|
|
1
|
+
import{buildPath as u,resolveParams as i,useRoute as m}from"./useRoute.js";import{RouteProvider as s,useRouteConfig as a}from"./RouteContext.js";import"react";import"react/jsx-runtime";export{s as RouteProvider,u as buildPath,i as resolveParams,m as useRoute,a as useRouteConfig};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
"use client";import{useMemo as f}from"react";import{
|
|
1
|
+
"use client";import{useMemo as f}from"react";import{useRouteConfig as d}from"./RouteContext.js";import"react/jsx-runtime";const m="/account/authenticate";function c(e,r){return r?e.replace(/:([a-zA-Z_][a-zA-Z0-9_]*)/g,(o,t)=>{const n=r[t];if(n===void 0)throw new Error(`useRoute: missing required param "${t}" for path "${e}"`);return encodeURIComponent(n)}):e}function h(e){return/:([a-zA-Z_][a-zA-Z0-9_]*)/.test(e)}function R(e,r){return c(e.path,r)}function _(e,r,o){const{isLoggedIn:t,pathname:n}=d();return f(()=>{const{path:u,isProtected:s,name:a="unknown"}=e;if(h(u)&&!r)throw new Error(`useRoute: params required for route "${a}" with path "${u}"`);const i=c(u,r);return o?.returnHere?`${i}?redirect=${encodeURIComponent(n??"/")}`:s&&!t?`${m}?redirect=${encodeURIComponent(i)}`:i},[e,r,o?.returnHere,n,t])}export{R as buildPath,c as resolveParams,_ as useRoute};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './useRoute/index';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './useRoute/index.js';
|
package/next/Image.d.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { ImageProps as NextImageProps } from 'next/image';
|
|
2
|
+
export type ExtendedImageProps = NextImageProps & {
|
|
3
|
+
/** Reduce opacity to 50% (e.g., while waiting for data) */
|
|
4
|
+
dimmed?: boolean;
|
|
5
|
+
/** Hide image completely — opacity 0 (e.g., before grid rearrange) */
|
|
6
|
+
hidden?: boolean;
|
|
7
|
+
/** Fires when a new image has loaded after a src change */
|
|
8
|
+
onReady?: () => void;
|
|
9
|
+
};
|
|
10
|
+
export declare function Image({ src, className, style, dimmed, hidden, onReady, onLoad: externalOnLoad, ...rest }: ExtendedImageProps): import("react/jsx-runtime").JSX.Element;
|
|
11
|
+
export default Image;
|
package/next/Image.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use client";import{jsx as o}from"react/jsx-runtime";import R from"next/image";import{useState as x,useRef as z,Component as T}from"react";import{cn as K}from"../utils/cn.js";import{useManagedOpacity as L,useImageStyle as j,getSrcKey as c,resolveSrc as u}from"../components/Image/imageShared.js";const m=new Set;class B extends T{constructor(){super(...arguments),this.state={hasError:!1}}static getDerivedStateFromError(){return{hasError:!0}}render(){if(this.state.hasError){m.add(c(this.props.src));const{src:i,alt:n,className:a,style:s,width:h,height:r}=this.props;return o("img",{src:u(i),alt:n??"",className:a,style:s,width:h,height:r,loading:"lazy"})}return this.props.children}}function P({src:e,className:i,style:n,dimmed:a,hidden:s,onReady:h,onLoad:r,...t}){const[g,p]=x(null),l=z(),{managed:f,setLoadingImg:y,onReadyRef:S,opacityStyle:w}=L({src:e,dimmed:a,hidden:s,onReady:h}),E="width"in t&&"height"in t&&!("fill"in t),d=j(n,E),v=c(e);return g===e||m.has(v)?o("img",{src:u(e),alt:t.alt??"",className:i,style:d,width:"width"in t?t.width:void 0,height:"height"in t?t.height:void 0,loading:"lazy"}):o(B,{src:e,alt:t.alt,className:i,style:d,width:"width"in t?t.width:void 0,height:"height"in t?t.height:void 0,children:o(R,{src:e,className:K(f&&"transition-opacity duration-200 ease-in-out",i),style:{...d,...w},onError:()=>{l.current=setTimeout(()=>p(e),2e3)},onLoad:I=>{clearTimeout(l.current),y(!1),S.current?.(),typeof r=="function"&&r(I)},...t})})}export{P as Image,P as default};
|
package/next/index.d.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Next-specific entry point (CTAP-1125).
|
|
3
|
+
*
|
|
4
|
+
* Everything in here imports from `next/*`. It is the ONLY part of the package
|
|
5
|
+
* that does, which is what lets `next` be an optional peer: a consumer that
|
|
6
|
+
* never imports `@clicktap/ui/next` gets zero `next` in its dependency tree.
|
|
7
|
+
*/
|
|
8
|
+
export { Image, type ExtendedImageProps } from './Image';
|
package/next/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{Image as a}from"./Image.js";import"react/jsx-runtime";import"next/image";import"react";import"../utils/cn.js";import"../components/Image/imageShared.js";export{a as Image};
|