@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.
Files changed (237) hide show
  1. package/components/Accordion/Accordion.js +1 -1
  2. package/components/Accordion.d.ts +1 -0
  3. package/components/Accordion.js +1 -0
  4. package/components/AssetRenderer/renderers/DownloadLink.d.ts +1 -1
  5. package/components/AssetRenderer/renderers.d.ts +1 -0
  6. package/components/AssetRenderer/renderers.js +1 -0
  7. package/components/AssetRenderer.d.ts +1 -0
  8. package/components/AssetRenderer.js +1 -0
  9. package/components/Avatar/Avatar.js +1 -1
  10. package/components/Avatar.d.ts +1 -0
  11. package/components/Avatar.js +1 -0
  12. package/components/Badge/Badge.js +1 -1
  13. package/components/Badge.d.ts +1 -0
  14. package/components/Badge.js +1 -0
  15. package/components/Breadcrumbs/BreadcrumbLink.js +1 -1
  16. package/components/Breadcrumbs.d.ts +1 -0
  17. package/components/Breadcrumbs.js +1 -0
  18. package/components/Button/Button.js +1 -1
  19. package/components/Button/ButtonLoadingContent.js +1 -1
  20. package/components/Button/index.js +1 -1
  21. package/components/Button.d.ts +1 -0
  22. package/components/Button.js +1 -0
  23. package/components/Card.d.ts +1 -0
  24. package/components/Card.js +1 -0
  25. package/components/Checkbox/Checkbox.js +1 -1
  26. package/components/Checkbox.d.ts +1 -0
  27. package/components/Checkbox.js +1 -0
  28. package/components/Collapsible.d.ts +1 -0
  29. package/components/Collapsible.js +1 -0
  30. package/components/Collection/AttributeDataProvider.d.ts +1 -0
  31. package/components/Collection/AttributeDataProvider.js +1 -0
  32. package/components/Collection/CollectionFilterChips/CollectionFilterChips.js +1 -1
  33. package/components/Collection/CollectionFilterChips/index.js +1 -1
  34. package/components/Collection/CollectionFilterChips.d.ts +1 -0
  35. package/components/Collection/CollectionFilterChips.js +1 -0
  36. package/components/Collection/CollectionFilters/CollectionFilters.js +1 -1
  37. package/components/Collection/CollectionFilters/index.js +1 -1
  38. package/components/Collection/CollectionFilters.d.ts +1 -0
  39. package/components/Collection/CollectionFilters.js +1 -0
  40. package/components/Collection/CollectionToolbar/CollectionToolbar.js +1 -1
  41. package/components/Collection/CollectionToolbar/index.js +1 -1
  42. package/components/Collection/CollectionToolbar.d.ts +1 -0
  43. package/components/Collection/CollectionToolbar.js +1 -0
  44. package/components/Collection/EmptyResults/EmptyResults.js +1 -1
  45. package/components/Collection/EmptyResults.d.ts +1 -0
  46. package/components/Collection/EmptyResults.js +1 -0
  47. package/components/Collection/FilterAccordionTitle/FilterAccordionTitle.js +1 -1
  48. package/components/Collection/FilterAccordionTitle.d.ts +1 -0
  49. package/components/Collection/FilterAccordionTitle.js +1 -0
  50. package/components/Collection/FilterRenderer/Renderer/Boolean.js +1 -1
  51. package/components/Collection/FilterRenderer/Renderer/Option.js +1 -1
  52. package/components/Collection/FilterRenderer/Renderer/Range.js +1 -1
  53. package/components/Collection/FilterRenderer/Renderer/Swatch/SwatchColor.js +1 -1
  54. package/components/Collection/FilterRenderer/Renderer/Swatch/SwatchImage.js +1 -1
  55. package/components/Collection/FilterRenderer/Renderer/Swatch/SwatchText.js +1 -1
  56. package/components/Collection/FilterRenderer/Renderer.d.ts +1 -0
  57. package/components/Collection/FilterRenderer/Renderer.js +1 -0
  58. package/components/Collection/FilterRenderer.d.ts +1 -0
  59. package/components/Collection/FilterRenderer.js +1 -0
  60. package/components/Collection/ProductCardSwatches/ProductCardSwatches.js +1 -1
  61. package/components/Collection/ProductCardSwatches.d.ts +1 -0
  62. package/components/Collection/ProductCardSwatches.js +1 -0
  63. package/components/Collection/ProductPrice.d.ts +1 -0
  64. package/components/Collection/ProductPrice.js +1 -0
  65. package/components/Collection/index.js +1 -1
  66. package/components/Collection.d.ts +1 -0
  67. package/components/Collection.js +1 -0
  68. package/components/Container.d.ts +1 -0
  69. package/components/Container.js +1 -0
  70. package/components/ContextMenu/ContextMenu.js +1 -1
  71. package/components/ContextMenu.d.ts +1 -0
  72. package/components/ContextMenu.js +1 -0
  73. package/components/CreditCardExpirationInput/CreditCardExpirationInput.js +1 -1
  74. package/components/CreditCardExpirationInput.d.ts +1 -0
  75. package/components/CreditCardExpirationInput.js +1 -0
  76. package/components/CreditCardInput/CreditCardInput.js +1 -1
  77. package/components/CreditCardInput.d.ts +1 -0
  78. package/components/CreditCardInput.js +1 -0
  79. package/components/DateInput/DateInput.js +1 -1
  80. package/components/DateInput.d.ts +1 -0
  81. package/components/DateInput.js +1 -0
  82. package/components/DateTimeFormat/DateTimeFormat.d.ts +1 -1
  83. package/components/DateTimeFormat.d.ts +1 -0
  84. package/components/DateTimeFormat.js +1 -0
  85. package/components/Dialog/Dialog.js +1 -1
  86. package/components/Dialog.d.ts +1 -0
  87. package/components/Dialog.js +1 -0
  88. package/components/DialogTrigger.d.ts +1 -0
  89. package/components/DialogTrigger.js +1 -0
  90. package/components/Divider.d.ts +1 -0
  91. package/components/Divider.js +1 -0
  92. package/components/DobInput/DobInput.js +1 -1
  93. package/components/DobInput.d.ts +1 -0
  94. package/components/DobInput.js +1 -0
  95. package/components/Drawer/Drawer.js +1 -1
  96. package/components/Drawer.d.ts +1 -0
  97. package/components/Drawer.js +1 -0
  98. package/components/FeatureGate/AccessDenied.d.ts +2 -2
  99. package/components/FeatureGate/AccessDenied.js +1 -1
  100. package/components/FeatureGate/FeatureGate.d.ts +1 -1
  101. package/components/FeatureGate/FeatureGate.js +1 -1
  102. package/components/FeatureGate/PermissionRequired.d.ts +1 -1
  103. package/components/FeatureGate/PermissionRequired.js +1 -1
  104. package/components/FeatureGate/UpgradePrompt.d.ts +1 -1
  105. package/components/FeatureGate/UpgradePrompt.js +1 -1
  106. package/components/Icon/NoEntryIcon.js +1 -1
  107. package/components/Icon/StarIcon.js +1 -1
  108. package/components/Icon.d.ts +1 -0
  109. package/components/Icon.js +1 -0
  110. package/components/Image/Image.d.ts +44 -3
  111. package/components/Image/Image.js +1 -1
  112. package/components/Image/ImageContext.d.ts +30 -0
  113. package/components/Image/ImageContext.js +1 -0
  114. package/components/Image/imageShared.d.ts +74 -0
  115. package/components/Image/imageShared.js +1 -0
  116. package/components/Image/index.d.ts +3 -1
  117. package/components/Image/index.js +1 -1
  118. package/components/Image.d.ts +1 -0
  119. package/components/Image.js +1 -0
  120. package/components/Input/Input.js +1 -1
  121. package/components/Input.d.ts +1 -0
  122. package/components/Input.js +1 -0
  123. package/components/Link/Link.js +1 -1
  124. package/components/Link.d.ts +1 -0
  125. package/components/Link.js +1 -0
  126. package/components/LinkAsButton/LinkAsButton.js +1 -1
  127. package/components/LinkAsButton/index.js +1 -1
  128. package/components/LinkAsButton.d.ts +1 -0
  129. package/components/LinkAsButton.js +1 -0
  130. package/components/Loader/CircularEasing.js +1 -1
  131. package/components/Loader/Pulse.js +1 -1
  132. package/components/Loader/index.js +1 -1
  133. package/components/Loader.d.ts +1 -0
  134. package/components/Loader.js +1 -0
  135. package/components/Menu.d.ts +1 -0
  136. package/components/Menu.js +1 -0
  137. package/components/Meter/Meter.js +1 -1
  138. package/components/Meter.d.ts +1 -0
  139. package/components/Meter.js +1 -0
  140. package/components/Modal.d.ts +1 -0
  141. package/components/Modal.js +1 -0
  142. package/components/ModalOverlay.d.ts +1 -0
  143. package/components/ModalOverlay.js +1 -0
  144. package/components/NumberFormat/NumberFormat.d.ts +1 -1
  145. package/components/NumberFormat.d.ts +1 -0
  146. package/components/NumberFormat.js +1 -0
  147. package/components/NumberInput/NumberInput.js +1 -1
  148. package/components/NumberInput.d.ts +1 -0
  149. package/components/NumberInput.js +1 -0
  150. package/components/NumberTicker.d.ts +1 -0
  151. package/components/NumberTicker.js +1 -0
  152. package/components/Pagination/Pagination.js +1 -1
  153. package/components/Pagination.d.ts +1 -0
  154. package/components/Pagination.js +1 -0
  155. package/components/PasswordCheck/PasswordCheck.js +1 -1
  156. package/components/PasswordCheck.d.ts +1 -0
  157. package/components/PasswordCheck.js +1 -0
  158. package/components/PhoneInput/PhoneInput.js +1 -1
  159. package/components/PhoneInput.d.ts +1 -0
  160. package/components/PhoneInput.js +1 -0
  161. package/components/PinInput/PinInput.js +1 -1
  162. package/components/PinInput.d.ts +1 -0
  163. package/components/PinInput.js +1 -0
  164. package/components/Progressbar/LinearProgressbar.js +1 -1
  165. package/components/Progressbar.d.ts +1 -0
  166. package/components/Progressbar.js +1 -0
  167. package/components/Radio/Radio.js +1 -1
  168. package/components/Radio.d.ts +1 -0
  169. package/components/Radio.js +1 -0
  170. package/components/RadioGroup/RadioGroup.js +1 -1
  171. package/components/RadioGroup.d.ts +1 -0
  172. package/components/RadioGroup.js +1 -0
  173. package/components/ResendCode.d.ts +1 -0
  174. package/components/ResendCode.js +1 -0
  175. package/components/Select/ButtonSelect.js +1 -1
  176. package/components/Select/ComboBoxSelect.js +1 -1
  177. package/components/Select/MultiButtonSelect.js +1 -1
  178. package/components/Select/Select.js +1 -1
  179. package/components/Select/index.js +1 -1
  180. package/components/Select/parts.js +1 -1
  181. package/components/Select.d.ts +1 -0
  182. package/components/Select.js +1 -0
  183. package/components/Skeleton.d.ts +1 -0
  184. package/components/Skeleton.js +1 -0
  185. package/components/Slider/Slider.js +1 -1
  186. package/components/Slider.d.ts +1 -0
  187. package/components/Slider.js +1 -0
  188. package/components/Switch/Switch.js +1 -1
  189. package/components/Switch.d.ts +1 -0
  190. package/components/Switch.js +1 -0
  191. package/components/Table.d.ts +1 -0
  192. package/components/Table.js +1 -0
  193. package/components/Tabs/Tab.js +1 -1
  194. package/components/Tabs/TabList.js +1 -1
  195. package/components/Tabs.d.ts +1 -0
  196. package/components/Tabs.js +1 -0
  197. package/components/Time.d.ts +1 -0
  198. package/components/Time.js +1 -0
  199. package/components/TimeInput/TimeInput.js +1 -1
  200. package/components/TimeInput.d.ts +1 -0
  201. package/components/TimeInput.js +1 -0
  202. package/components/Timezone.d.ts +1 -0
  203. package/components/Timezone.js +1 -0
  204. package/components/ToggleButton/ToggleButton.js +1 -1
  205. package/components/ToggleButton.d.ts +1 -0
  206. package/components/ToggleButton.js +1 -0
  207. package/components/Tooltip.d.ts +1 -0
  208. package/components/Tooltip.js +1 -0
  209. package/components/UploadImage/UploadImage.js +1 -1
  210. package/components/UploadImage/index.js +1 -1
  211. package/components/UploadImage.d.ts +1 -0
  212. package/components/UploadImage.js +1 -0
  213. package/docs/migrations/2026/04/2026-04-24-image-placeholder-frontend-fallback.md +3 -3
  214. package/docs/migrations/2026/04/2026-04-30-prune-invalid-pdp-selections.md +2 -2
  215. package/docs/migrations/2026/04/2026-04-30-top-down-pdp-option-dependency.md +2 -2
  216. package/docs/migrations/2026/08/2026-08-06-clicktap-ui-image-next-subpath.md +101 -0
  217. package/docs/migrations/2026/08/2026-08-06-clicktap-ui-loaders-drop-framer-motion.md +77 -0
  218. package/docs/migrations/2026/08/2026-08-06-clicktap-ui-semantic-tokens.md +106 -0
  219. package/docs/migrations/README.md +1 -0
  220. package/hooks/useCollectionState.d.ts +1 -0
  221. package/hooks/useCollectionState.js +1 -0
  222. package/hooks/useRoute/RouteContext.d.ts +8 -0
  223. package/hooks/useRoute/index.js +1 -1
  224. package/hooks/useRoute/useRoute.js +1 -1
  225. package/hooks/useRoute.d.ts +1 -0
  226. package/hooks/useRoute.js +1 -0
  227. package/next/Image.d.ts +11 -0
  228. package/next/Image.js +1 -0
  229. package/next/index.d.ts +8 -0
  230. package/next/index.js +1 -0
  231. package/package.json +62 -6
  232. package/theme.css +162 -0
  233. package/utils/collection.d.ts +1 -0
  234. package/utils/collection.js +1 -0
  235. package/utils/variantOptions.d.ts +1 -0
  236. package/utils/variantOptions.js +1 -0
  237. 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';
@@ -1 +1 @@
1
- "use client";import{jsx as r,jsxs as u,Fragment as b}from"react/jsx-runtime";import{useContext as c}from"react";import{Tab as m}from"react-aria-components";import{motion as f}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:l,children:t,...s}){return r(m,{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-slate-500",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-slate-400",(e==="underline"||e==="outline")&&"selected:text-slate-800",(e==="solid"||e==="enclosed")&&"selected:text-slate-100",l),...s,children:t})}function w({variant:o="base",children:e,className:l,classNames:t,...s}){const d=c(p);return r(x,{orientation:d,variant:o,className:l,...s,children:n=>u(b,{children:[typeof e=="function"?e({defaultChildren:void 0,...n}):e,(n.isFocusVisible||n.isSelected)&&r(f.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-slate-800",o==="underline"&&"rounded-lg bg-slate-800 mix-blend-color bottom-0",o==="underline"&&d==="horizontal"&&"left-0 w-full h-px",o==="underline"&&d==="vertical"&&"right-0 w-px h-full",o==="enclosed"&&"inset-0 border-solid border-1 border-slate-800 -mb-px border-b-0 rounded-t-lg rounded-b-none bg-white mix-blend-difference",o==="base"&&"hidden",t?.overflow),layoutId:o,transition:{type:"spring",bounce:.2,duration:.75}})]})})}export{x as BaseTab,w as Tab,w as default};
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,...a}){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-slate-200 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,...a,children:i||(b=>r(m,{orientation:o,children:b.title}))})}export{T as TabList,T as default};
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 n,jsx as i}from"react/jsx-runtime";import{TimeField as u,Label as c,DateInput as f,DateSegment as x,Text as b,FieldError as m}from"react-aria-components";import{cn as e}from"../../utils/cn.js";function w({label:a,description:l,errorMessage:r,className:o,classNames:t,...d}){return n(u,{className:e("text-slate-900",o),...d,children:[i(c,{className:e("flex text-slate-500 text-sm",t?.label),children:a}),i(f,{className:e("flex items-center","border-solid border border-slate-300 rounded-md","text-sm text-slate-900","h-10 px-1 py-0 m-0 w-full","bg-white","transition-all duration-200 ease-in-out","hover:border-slate-400","focus-within:outline-2 focus-within:outline focus-within:outline-slate-200 focus-within:border-slate-400","disabled:border-slate-200 disabled:bg-slate-100","invalid:border-red-500 invalid:bg-red-100 invalid:text-red-600","invalid:hover:border-red-600 invalid:focus-within:border-red-600 invalid:focus-within:outline-red-200",t?.input),children:s=>i(x,{segment:s,className:e("p-1 tabular-nums text-end text-slate-900","invalid:text-red-500","disabled:cursor-default disabled:select-none disabled:text-slate-400","focus:text-slate-900 focus:bg-slate-200 focus:outline-0 focus:rounded-md focus:caret-transparent focus:invalid:bg-red-500 focus:invalid:text-white","type-literal:p-0.5","data-[placeholder]:text-slate-400 data-[placeholder]:invalid:text-red-500 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)})}),l&&i(b,{className:e("flex text-slate-500 text-sm",t?.description),slot:"description",children:l}),i(m,{className:e("flex text-red-500 text-sm",t?.error),children:r})]})}export{w as TimeInput,w as default};
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 l}from"react/jsx-runtime";import{ToggleButton as n}from"react-aria-components";import{cn as b}from"../../utils/cn.js";function g({children:o,size:e="md",variant:t="solid",className:r,...d}){return l(n,{className:a=>{const{isDisabled:s}=a;return b("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-slate-200 pressed:scale-95",{"h-8":e==="sm","h-10":e==="md","h-12":e==="lg"},[t==="ghost"&&["bg-transparent hover:bg-transparent focus:bg-transparent disabled:bg-transparent","border-transparent hover:border-transparent focus:border-transparent disabled:border-transparent","text-slate-900 disabled:text-slate-400",s&&["disabled:text-slate-900","opacity-75"]],t==="outline"&&["bg-transparent hover:bg-transparent focus:bg-transparent disabled:bg-transparent","border-slate-300 hover:border-slate-400 focus:border-slate-400 disabled:border-slate-200","text-slate-900 disabled:text-slate-500",s&&["disabled:border-slate-300","disabled:text-slate-900","opacity-75"]],t==="solid"&&["bg-slate-800 hover:bg-slate-900 focus:bg-slate-900 disabled:bg-slate-900","border-slate-800 hover:border-slate-900 focus:border-slate-900 disabled:border-slate-200","text-white disabled:text-slate-400",s&&["disabled:border-slate-900","disabled:text-white","opacity-75"]]],typeof r=="function"?r(a):r)},...d,children:o})}export{g as ToggleButton,g as default};
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 p,jsx as e}from"react/jsx-runtime";import{forwardRef as y,useState as v}from"react";import{motion as j}from"framer-motion";import V from"next/image";import{FieldError as _}from"react-aria-components";import{cn as a}from"../../utils/cn.js";import k from"../../hooks/useIsClient.js";import{Skeleton as u}from"../Skeleton/Skeleton.js";function U({hasTitle:r,className:s}){return l("div",{className:"w-full h-full flex flex-col gap-4",children:[r&&e(u,{className:"w-1/2 h-8 mx-auto rounded-md z-20"}),e(u,{className:a("w-full h-56 rounded-md z-20 relative",s)})]})}const S=y(({title:r,description:s="Preview will display here.",fileExtension:d,actionTitle:g="select file",variant:i,errorMessage:h,defaultImagePath:n,classNames:f,...c},x)=>{const b=k(),[t,w]=v({src:null,alt:null}),C=!!h,N=d?d.split(",").map(o=>`image/${o.trim()}`).join(", "):"image/*";return l("div",{className:a("w-full h-full","flex flex-col gap-4"),children:[b?l(p,{children:[r&&e("h6",{className:a("text-center","text-2xl","m-0 mb-4"),children:r}),l("div",{className:a("w-full h-full","flex flex-col justify-between gap-5 items-center",["bg-slate-100",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:m}}=o;m&&w({src:URL.createObjectURL(m[0]),alt:m[0].name})},className:a("inline-flex justify-center items-center","uppercase text-base border-slate-200","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-slate-950","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:x,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(p,{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(V,{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-slate-950 text-center",children:t.alt||s})]})]})]}):e(U,{hasTitle:!!r,className:f?.skeleton}),C&&e(_,{className:a("-mt-2","self-start"),children:h})]})});export{S as UploadImage,S as default};
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 g}from"./UploadImage.js";import"react/jsx-runtime";import"react";import"framer-motion";import"next/image";import"react-aria-components";import"../../utils/cn.js";import"../../hooks/useIsClient.js";import"../Skeleton/Skeleton.js";export{g as UploadImage};
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;
@@ -1 +1 @@
1
- import{buildPath as m,resolveParams as u,useRoute as p}from"./useRoute.js";import{RouteProvider as a,useRouteConfig as f}from"./RouteContext.js";import"react";import"next/navigation";import"react/jsx-runtime";export{a as RouteProvider,m as buildPath,u as resolveParams,p as useRoute,f as useRouteConfig};
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{usePathname as m}from"next/navigation";import{useRouteConfig as d}from"./RouteContext.js";import"react/jsx-runtime";const h="/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 p(e){return/:([a-zA-Z_][a-zA-Z0-9_]*)/.test(e)}function _(e,r){return c(e.path,r)}function l(e,r,o){const t=m(),{isLoggedIn:n}=d();return f(()=>{const{path:u,isProtected:s,name:a="unknown"}=e;if(p(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(t??"/")}`:s&&!n?`${h}?redirect=${encodeURIComponent(i)}`:i},[e,r,o?.returnHere,t,n])}export{_ as buildPath,c as resolveParams,l as useRoute};
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';
@@ -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};
@@ -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};