@clicktap/ui 1.2.1 → 2.0.4

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 (47) hide show
  1. package/components/Accordion/Accordion.d.ts +76 -2
  2. package/components/Accordion/Accordion.js +1 -1
  3. package/components/Accordion/index.d.ts +2 -3
  4. package/components/Accordion/index.js +1 -1
  5. package/components/Badge/Badge.js +1 -1
  6. package/components/Badge/Badge.types.d.ts +27 -2
  7. package/components/Badge/index.js +1 -1
  8. package/components/Collapsible/Collapsible.js +1 -1
  9. package/components/Collection/CollectionFilters/CollectionFilters.js +1 -1
  10. package/components/Collection/CollectionFilters/index.js +1 -1
  11. package/components/Collection/CollectionToolbar/CollectionToolbar.js +1 -1
  12. package/components/Collection/ProductCardSwatches/ProductCardSwatches.js +1 -1
  13. package/components/Collection/index.js +1 -1
  14. package/components/ContextMenu/ContextMenu.js +1 -1
  15. package/components/DialogTrigger/DialogTrigger.d.ts +18 -10
  16. package/components/DialogTrigger/DialogTrigger.js +1 -1
  17. package/components/DialogTrigger/index.js +1 -1
  18. package/components/Drawer/Drawer.js +1 -1
  19. package/components/Menu/Menu.js +1 -1
  20. package/components/Meter/Meter.js +1 -1
  21. package/components/NumberTicker/DigitResolver.js +1 -1
  22. package/components/NumberTicker/NumberTicker.js +1 -1
  23. package/components/Pagination/Pagination.d.ts +27 -2
  24. package/components/Pagination/Pagination.js +1 -1
  25. package/components/Pagination/index.js +1 -1
  26. package/components/Progressbar/LinearProgressbar.js +1 -1
  27. package/components/Tabs/Tab.js +1 -1
  28. package/components/Tooltip/Tooltip.d.ts +9 -1
  29. package/components/Tooltip/Tooltip.js +1 -1
  30. package/components/Tooltip/Tooltip.types.d.ts +21 -2
  31. package/components/Tooltip/index.js +1 -1
  32. package/components/UploadImage/UploadImage.js +1 -1
  33. package/docs/migrations/2026/08/ui-2026-08-21-graphql-client-string-documents.md +45 -0
  34. package/docs/migrations/2026/08/ui-2026-08-21-graphql-peer-covered.md +26 -0
  35. package/docs/migrations/2026/08/ui-2026-08-21-heroui-removed.md +54 -0
  36. package/docs/migrations/2026/08/ui-2026-08-21-lazymotion-m-components.md +54 -0
  37. package/docs/migrations/2026/08/ui-2026-08-21-modal-triggers-wired-explicitly.md +43 -0
  38. package/docs/migrations/2026/08/ui-2026-08-21-next-image-fallback-keeps-fill.md +38 -0
  39. package/docs/migrations/2026/08/ui-2026-08-22-accordion-divider-between-items.md +30 -0
  40. package/docs/migrations/2026/08/ui-2026-08-22-accordion-divider-class-ownership.md +14 -0
  41. package/docs/migrations/2026/08/ui-2026-08-22-collection-accessibility-fixes.md +28 -0
  42. package/docs/migrations/2026/08/ui-2026-08-22-react-aria-moves-as-one-version-set.md +46 -0
  43. package/docs/migrations/index.json +101 -0
  44. package/next/Image.js +1 -1
  45. package/package.json +10 -38
  46. package/utils/createAuthAwareGraphqlClient.d.ts +104 -16
  47. package/utils/createAuthAwareGraphqlClient.js +1 -56
@@ -0,0 +1,43 @@
1
+ # Modal Triggers Are Wired Explicitly; Overlay Animation Back on `motion`
2
+
3
+ **Date:** 2026-08-21
4
+ **Package:** `@clicktap/ui`
5
+ **Affects:** DialogTrigger, ModalOverlay, Dialog
6
+ **Classification:** BUGFIX
7
+ **Breaking:** No. Same component APIs; consumer `onPress` on the trigger child still runs (chained).
8
+ **Ticket:** CTAP-1383
9
+
10
+ ## Problems
11
+
12
+ 1. **Trigger-driven modals never opened** — every uncontrolled `DialogTrigger` modal
13
+ (account address/payment create-edit-delete, delete confirmations) rendered an inert
14
+ button: react-aria-components wires its trigger through `PressResponderContext`, and in
15
+ this app's production bundles that registration silently never reaches the Button (no
16
+ aria attributes applied, press never toggles state) — with a verified-single instance
17
+ of react-aria-components, @react-aria/interactions, react, and framer-motion in the
18
+ bundle, so it is NOT the dual-package hazard CTAP-1210 fixed. Every working modal in
19
+ the app (FilterButton, RegionSelectAction, CartDrawer) works because it passes an
20
+ explicit `onPress`/controlled `isOpen`. Evidence says this has been broken on master
21
+ too (the same missing aria attributes on QA's trigger buttons).
22
+ 2. **`m.create` broke controlled modals** — the CTAP-1383 LazyMotion conversion switched
23
+ `motion.create(...)` to `m.create(...)` in ModalOverlay/Dialog. In production browser
24
+ bundles the m-created overlay never mounts its portal (works in jsdom; browser-only).
25
+ Verified by A/B builds: identical code with `motion.create` opens, `m.create` does not.
26
+
27
+ ## Changes
28
+
29
+ - `DialogTrigger` no longer wraps react-aria-components' DialogTrigger. It owns a
30
+ `useOverlayTriggerState`, provides it on `OverlayTriggerStateContext` (which both our
31
+ ModalOverlay and react-aria's read), and clones the first child with an explicit
32
+ `onPress` that toggles the state — the mechanism the rest of the app already proves
33
+ out. A consumer-supplied `onPress` is chained before the toggle. The `useDialogTrigger`
34
+ shim is unchanged.
35
+ - `ModalOverlay`/`Dialog` are back on `motion.create` (costs ~18 KB gz of initial JS vs
36
+ the m form — the price of modals that open). Root-causing why `m.create` + react-aria
37
+ overlays fail in production bundles is a follow-up; if solved, the m form can return.
38
+
39
+ ## What to check
40
+
41
+ Any `DialogTrigger` + `Button` + `ModalOverlay` combination opens on press — uncontrolled
42
+ (account pages) and controlled (PLP filter) alike. Dialog `aria-label`s are now the only
43
+ labelling source (the react-aria trigger-id fallback is gone) — our dialogs all pass one.
@@ -0,0 +1,38 @@
1
+ # next Image: the optimizer-failure fallback keeps fill semantics
2
+
3
+ **Date:** 2026-08-21
4
+ **Package:** `@clicktap/ui`
5
+ **Ticket:** CTAP-1376 (found during the CTAP-1375 QA incident)
6
+ **Affects:** `@clicktap/ui/next/Image` — every `fill`-mode usage. No consumer code change.
7
+ **Classification:** BUGFIX
8
+ **Breaking:** No
9
+
10
+ ## The failure this removes
11
+
12
+ When `next/image` optimization fails, this wrapper bypasses it and renders the
13
+ raw src as a plain `<img>` (the `onError` debounce path and the render-error
14
+ boundary). Both forwarded `className`, `style` and `width`/`height` — but a
15
+ `fill` image has no width/height props, and its positioning
16
+ (`position:absolute; inset:0; height/width:100%`) is inline style **NextImage
17
+ itself adds**, which the fallback bypassed along with the optimizer.
18
+
19
+ So a fill image whose optimizer fetch failed degraded catastrophically instead
20
+ of gracefully: before the raw src loaded it sat as a broken icon at its
21
+ container's top-left; once loaded it rendered at NATURAL size — a 2880px
22
+ category hero escaping its `relative overflow-hidden` banner and filling
23
+ multiple viewports. With an environment-wide optimizer failure (CTAP-1375:
24
+ media URLs pointing at an unreachable host) that was every banner on the site.
25
+
26
+ ## The fix
27
+
28
+ When the props include `fill`, both fallback `<img>`s re-apply the same inline
29
+ styles NextImage would have (`position:absolute; height/width:100%;
30
+ left/top/right/bottom:0`), merged under the caller's `style`. `object-fit`
31
+ still arrives via the forwarded `className` (`object-cover` etc.). Sized
32
+ images (width/height props) are byte-for-byte unchanged.
33
+
34
+ ## What you need to do
35
+
36
+ Nothing — take the release. If you have ever worked around this with wrapper
37
+ divs or manual absolute positioning around a fill-mode `Image` specifically
38
+ for its error state, that can come off.
@@ -0,0 +1,30 @@
1
+ # Accordion Divider Renders BETWEEN Items; react-aria Stack Aligned
2
+
3
+ **Date:** 2026-08-22
4
+ **Package:** `@clicktap/ui`
5
+ **Affects:** Accordion consumers using `dividerProps` as an inter-item spacer (checkout payment methods); every react-aria-components consumer
6
+ **Classification:** BUGFIX
7
+ **Breaking:** No (peer ranges raised: react-aria ^3.48, react-aria-components ^1.17, react-stately ^3.46).
8
+ **Ticket:** CTAP-1383
9
+
10
+ ## Problems
11
+
12
+ 1. **Divider inside the item box.** The CTAP-1383 Accordion rewrite rendered the
13
+ between-items `<hr>` INSIDE each Disclosure — inside the consumer's bordered
14
+ `itemClasses.base` box. The checkout payment list styles the divider as an invisible
15
+ `h-4` spacer meant as a GAP between boxes, so every payment method carried ~32px of
16
+ dead space under its title. heroui rendered dividers between items; the rewrite now
17
+ does too (a sibling after each Disclosure).
18
+ 2. **Mixed react-aria internals.** The repo pinned react-aria 3.46 / react-aria-components
19
+ 1.15 while pnpm resolved newer @react-aria internals into their closures. Production
20
+ bundles with that mix showed a family of RAC-internal failures jsdom never caught:
21
+ Table rows losing their cells on hover (account orders emptying), and the
22
+ PressResponder trigger wiring never reaching Buttons. Dev/peer versions now align on
23
+ the coherent set: react-aria 3.48.0, react-aria-components 1.17.0, react-stately
24
+ 3.46.0 — verified to fix the Table hover failure outright.
25
+
26
+ ## What to check
27
+
28
+ Payment methods render as compact bordered rows (divider = gap, not in-box padding).
29
+ Account orders survive hovering/navigating rows. Bordered/splitted Accordion variants
30
+ keep their internal spacing (those style the divider as a visible rule, unaffected).
@@ -0,0 +1,14 @@
1
+ # Accordion `dividerProps.className` Owns the Divider Outright
2
+
3
+ **Date:** 2026-08-22
4
+ **Package:** `@clicktap/ui`
5
+ **Affects:** Accordion consumers styling the inter-item divider
6
+ **Classification:** BUGFIX
7
+ **Breaking:** No.
8
+ **Ticket:** CTAP-1383
9
+
10
+ A consumer-supplied `dividerProps.className` now replaces the default divider classes
11
+ (spacing and color) instead of stacking on top of them. The checkout payment list
12
+ styles the divider as an invisible `h-4` spacer; with the default `my-2` still applied
13
+ the gap between items doubled to 32px. Consumers that pass no className keep the
14
+ default `my-2` rule.
@@ -0,0 +1,28 @@
1
+ # CollectionToolbar and ProductCardSwatches Accessibility Fixes
2
+
3
+ **Date:** 2026-08-22
4
+ **Package:** `@clicktap/ui`
5
+ **Affects:** `Collection/CollectionToolbar`, `Collection/ProductCardSwatches`
6
+ **Classification:** BUGFIX (accessibility)
7
+ **Breaking:** No. Visual change is 2px of extra gap between card swatches.
8
+ **Ticket:** CTAP-1380
9
+
10
+ ## Changes
11
+
12
+ **`CollectionToolbar` — invalid `aria-details` removed.** The mobile filter button carried
13
+ `aria-details="show filters"`. `aria-details` takes an **ID reference**, not text, so axe
14
+ flags the value as invalid (`aria-valid-attr-value`). The button already has a visible
15
+ "Filters" label, so the attribute was redundant too.
16
+
17
+ **`ProductCardSwatches` — swatch spacing meets WCAG 2.5.8.** The swatches are 16px, under
18
+ the 24px minimum target size. The standard's spacing exception allows a smaller target
19
+ when no neighbouring target falls inside a 24px-diameter circle centred on it — centres
20
+ at least 24px apart. The row used `gap-1.5` (6px), leaving centres 22px apart, so every
21
+ swatch failed `target-size` (37 elements on a single collection page). Now `gap-2` (8px),
22
+ which puts centres at exactly 24px. The swatches themselves are unchanged.
23
+
24
+ ## What to check
25
+
26
+ Swatch rows are 2px wider per gap — confirm no wrapping regression at the narrowest
27
+ breakpoint. Consumers overriding the swatch container's spacing should re-check they
28
+ still satisfy the 24px centre-to-centre rule.
@@ -0,0 +1,46 @@
1
+ # react-aria Moves as One Version Set
2
+
3
+ **Date:** 2026-08-22
4
+ **Package:** `@clicktap/ui`
5
+ **Affects:** every consumer of react-aria-components — dependency policy, not an API change
6
+ **Classification:** COMPATIBILITY
7
+ **Read before updating:** never bump `react-aria`, `react-aria-components`, or `react-stately` independently — skewed internals fail silently, and only in production bundles.
8
+ **Ticket:** CTAP-1383
9
+
10
+ ## The finding
11
+
12
+ The repo pinned `react-aria-components@1.15.1` / `react-aria@3.46.0` while pnpm resolved
13
+ NEWER `@react-aria/*` subpackages into their closures (an ordinary lockfile drift — the
14
+ packages install fine, typecheck fine, and every jest suite passes). The mixed internals
15
+ produced a family of failures that exist **only in production browser bundles**:
16
+
17
+ - **Uncontrolled `DialogTrigger` modals were dead.** The PressResponder trigger wiring
18
+ never reached the Button: the trigger props arrived structurally but valueless, presses
19
+ never toggled the overlay state. Every account modal (address/payment create, edit,
20
+ delete confirmations) silently did nothing — on master too.
21
+ - **`Table` rows lost their cells on hover.** Sweeping the pointer across the account
22
+ orders table emptied every `<tr>` (row elements remain, children gone) — the "my orders
23
+ disappear when I hover them" report. Click-through and back-navigation compounded it.
24
+ - Likely related, unproven: `m.create()`-wrapped RAC overlays never mounting their portal
25
+ (see the modal-triggers guide) — same only-in-production signature.
26
+
27
+ None of this reproduces in jsdom. All package instances were verified single-copy in the
28
+ bundle (this is NOT the CTAP-1210 dual-package hazard), which is what makes the skew so
29
+ hard to see: the failure lives in version-mismatched internals of one coherent-looking
30
+ install.
31
+
32
+ ## The rule
33
+
34
+ `react-aria`, `react-aria-components`, and `react-stately` are released together and must
35
+ be adopted together. This package's devDependencies pin one coherent set (currently
36
+ 3.48.0 / 1.17.0 / 3.46.0) and its peer ranges floor consumers at the same set; the
37
+ reference app pins the identical versions. When bumping, take the trio from the same
38
+ react-aria release announcement and re-run the production-bundle checks below —
39
+ aligning to the coherent set fixed the Table failure outright with no code change.
40
+
41
+ ## How to verify (jsdom cannot)
42
+
43
+ Probe a production build in a real browser: open an uncontrolled DialogTrigger modal
44
+ (account → Add Address), hover-sweep the orders table rows and count `[role="row"]`
45
+ children, and diff `.pnpm` instance DIRS — key on the full directory name, not
46
+ `name@version`, or two peer-hash instances of the same version collapse into one.
@@ -1,4 +1,105 @@
1
1
  [
2
+ {
3
+ "path": "2026/08/ui-2026-08-22-react-aria-moves-as-one-version-set.md",
4
+ "module": "libs/frontend-modules/ui",
5
+ "package": "@clicktap/ui",
6
+ "date": "2026-08-22",
7
+ "title": "react-aria Moves as One Version Set",
8
+ "classification": "COMPATIBILITY",
9
+ "breaking": "",
10
+ "affects": "every consumer of react-aria-components — dependency policy, not an API change",
11
+ "read_before_updating": "never bump `react-aria`, `react-aria-components`, or `react-stately` independently — skewed internals fail silently, and only in production bundles."
12
+ },
13
+ {
14
+ "path": "2026/08/ui-2026-08-22-collection-accessibility-fixes.md",
15
+ "module": "libs/frontend-modules/ui",
16
+ "package": "@clicktap/ui",
17
+ "date": "2026-08-22",
18
+ "title": "CollectionToolbar and ProductCardSwatches Accessibility Fixes",
19
+ "classification": "BUGFIX (accessibility)",
20
+ "breaking": "No. Visual change is 2px of extra gap between card swatches.",
21
+ "affects": "`Collection/CollectionToolbar`, `Collection/ProductCardSwatches`"
22
+ },
23
+ {
24
+ "path": "2026/08/ui-2026-08-22-accordion-divider-class-ownership.md",
25
+ "module": "libs/frontend-modules/ui",
26
+ "package": "@clicktap/ui",
27
+ "date": "2026-08-22",
28
+ "title": "Accordion `dividerProps.className` Owns the Divider Outright",
29
+ "classification": "BUGFIX",
30
+ "breaking": "No.",
31
+ "affects": "Accordion consumers styling the inter-item divider"
32
+ },
33
+ {
34
+ "path": "2026/08/ui-2026-08-22-accordion-divider-between-items.md",
35
+ "module": "libs/frontend-modules/ui",
36
+ "package": "@clicktap/ui",
37
+ "date": "2026-08-22",
38
+ "title": "Accordion Divider Renders BETWEEN Items; react-aria Stack Aligned",
39
+ "classification": "BUGFIX",
40
+ "breaking": "No (peer ranges raised: react-aria ^3.48, react-aria-components ^1.17, react-stately ^3.46).",
41
+ "affects": "Accordion consumers using `dividerProps` as an inter-item spacer (checkout payment methods); every react-aria-components consumer"
42
+ },
43
+ {
44
+ "path": "2026/08/ui-2026-08-21-next-image-fallback-keeps-fill.md",
45
+ "module": "libs/frontend-modules/ui",
46
+ "package": "@clicktap/ui",
47
+ "date": "2026-08-21",
48
+ "title": "next Image: the optimizer-failure fallback keeps fill semantics",
49
+ "classification": "BUGFIX",
50
+ "breaking": "No",
51
+ "affects": "`@clicktap/ui/next/Image` — every `fill`-mode usage. No consumer code change."
52
+ },
53
+ {
54
+ "path": "2026/08/ui-2026-08-21-modal-triggers-wired-explicitly.md",
55
+ "module": "libs/frontend-modules/ui",
56
+ "package": "@clicktap/ui",
57
+ "date": "2026-08-21",
58
+ "title": "Modal Triggers Are Wired Explicitly; Overlay Animation Back on `motion`",
59
+ "classification": "BUGFIX",
60
+ "breaking": "No. Same component APIs; consumer `onPress` on the trigger child still runs (chained).",
61
+ "affects": "DialogTrigger, ModalOverlay, Dialog"
62
+ },
63
+ {
64
+ "path": "2026/08/ui-2026-08-21-lazymotion-m-components.md",
65
+ "module": "libs/frontend-modules/ui",
66
+ "package": "@clicktap/ui",
67
+ "date": "2026-08-21",
68
+ "title": "Animated Components Render `m.*` — Apps Must Provide LazyMotion Features",
69
+ "classification": "COMPATIBILITY",
70
+ "breaking": "Animations no-op (components still render and function) until the consuming app mounts a `LazyMotion` provider.",
71
+ "affects": "every animated component (ModalOverlay, Drawer, Dialog, Menu, ContextMenu, Tabs, Badge, Collapsible, NumberTicker, Meter, Progressbar, UploadImage)."
72
+ },
73
+ {
74
+ "path": "2026/08/ui-2026-08-21-heroui-removed.md",
75
+ "module": "libs/frontend-modules/ui",
76
+ "package": "@clicktap/ui",
77
+ "date": "2026-08-21",
78
+ "title": "The @heroui Dependency Is Gone — Accordion, Tooltip, Pagination, Badge Are Native",
79
+ "classification": "COMPATIBILITY",
80
+ "breaking": "API-compatible for every usage shape in the reference repo (inventoried below). Consumers importing `@heroui`/`@nextui-org` types directly must switch to this package's exports.",
81
+ "affects": "Accordion, Tooltip, Pagination, Badge. Peer/dev dependencies on `@heroui/*` removed."
82
+ },
83
+ {
84
+ "path": "2026/08/ui-2026-08-21-graphql-peer-covered.md",
85
+ "module": "libs/frontend-modules/ui",
86
+ "package": "@clicktap/ui",
87
+ "date": "2026-08-21",
88
+ "title": "`graphql` Peer Is Covered — No More Double Bundle",
89
+ "classification": "BUGFIX (dependency resolution)",
90
+ "breaking": "No. `graphql ^16.6.0` is an optional peer, matching `@clicktap/state`.",
91
+ "affects": "consumers' bundle size (~12 KB gz of duplicated `graphql`)"
92
+ },
93
+ {
94
+ "path": "2026/08/ui-2026-08-21-graphql-client-string-documents.md",
95
+ "module": "libs/frontend-modules/ui",
96
+ "package": "@clicktap/ui",
97
+ "date": "2026-08-21",
98
+ "title": "GraphQL Client Executes String Documents — graphql-request Removed",
99
+ "classification": "COMPATIBILITY (breaking)",
100
+ "breaking": "Yes. AST `TypedDocumentNode` documents are no longer accepted; the `GraphQLClient` re-export is gone; `ClientError` is now this module's own class.",
101
+ "affects": "`utils/createAuthAwareGraphqlClient` (public API)"
102
+ },
2
103
  {
3
104
  "path": "2026/08/ui-2026-08-14-zod-error-map.md",
4
105
  "module": "libs/frontend-modules/ui",
package/next/Image.js CHANGED
@@ -1 +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};
1
+ "use client";import{jsx as r}from"react/jsx-runtime";import F from"next/image";import{useState as R,useRef as T,Component as b}from"react";import{cn as x}from"../utils/cn.js";import{useManagedOpacity as z,useImageStyle as B,getSrcKey as u,resolveSrc as g}from"../components/Image/imageShared.js";const m=new Set,K={position:"absolute",height:"100%",width:"100%",left:0,top:0,right:0,bottom:0};class A extends b{constructor(){super(...arguments),this.state={hasError:!1}}static getDerivedStateFromError(){return{hasError:!0}}render(){if(this.state.hasError){m.add(u(this.props.src));const{src:e,alt:n,className:a,style:l,width:s,height:o}=this.props;return r("img",{src:g(e),alt:n??"",className:a,style:l,width:s,height:o,loading:"lazy"})}return this.props.children}}function N({src:i,className:e,style:n,dimmed:a,hidden:l,onReady:s,onLoad:o,...t}){const[f,p]=R(null),c=T(),{managed:y,setLoadingImg:S,onReadyRef:w,opacityStyle:E}=z({src:i,dimmed:a,hidden:l,onReady:s}),L="width"in t&&"height"in t&&!("fill"in t),h=B(n,L),d="fill"in t&&!!t.fill?{...K,...h}:h,v=u(i);return f===i||m.has(v)?r("img",{src:g(i),alt:t.alt??"",className:e,style:d,width:"width"in t?t.width:void 0,height:"height"in t?t.height:void 0,loading:"lazy"}):r(A,{src:i,alt:t.alt,className:e,style:d,width:"width"in t?t.width:void 0,height:"height"in t?t.height:void 0,children:r(F,{src:i,className:x(y&&"transition-opacity duration-200 ease-in-out",e),style:{...h,...E},onError:()=>{c.current=setTimeout(()=>p(i),2e3)},onLoad:I=>{clearTimeout(c.current),S(!1),w.current?.(),typeof o=="function"&&o(I)},...t})})}export{N as Image,N as default};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@clicktap/ui",
3
- "version": "1.2.1",
3
+ "version": "2.0.4",
4
4
  "private": false,
5
5
  "author": "Clicktap",
6
6
  "description": "A library of React UI components and low-level hooks.",
@@ -104,10 +104,6 @@
104
104
  "devDependencies": {
105
105
  "@floating-ui/react": "0.27.20",
106
106
  "@graphql-typed-document-node/core": "^3.2.0",
107
- "@heroui/accordion": "^2.2.0",
108
- "@heroui/badge": "^2.2.0",
109
- "@heroui/pagination": "^2.2.0",
110
- "@heroui/tooltip": "^2.2.0",
111
107
  "@nx/react": "20.5.0",
112
108
  "@nx/vite": "20.5.0",
113
109
  "@storybook/addon-actions": "^8.0.6",
@@ -117,15 +113,15 @@
117
113
  "@vitejs/plugin-react": "4.5.2",
118
114
  "framer-motion": "^11.18.0",
119
115
  "glob": "^11.0.0",
120
- "graphql-request": "^6.1.0",
116
+ "graphql": "16.12.0",
121
117
  "next": "14.2.35",
122
118
  "react": "18.3.1",
123
- "react-aria": "3.46.0",
124
- "react-aria-components": "1.15.1",
119
+ "react-aria": "3.48.0",
120
+ "react-aria-components": "1.17.0",
125
121
  "react-dom": "18.3.1",
126
122
  "react-hook-form": "^7.71.1",
127
123
  "react-international-phone": "^4.7.0",
128
- "react-stately": "3.44.0",
124
+ "react-stately": "3.46.0",
129
125
  "rollup-preserve-directives": "^1.1.3",
130
126
  "tailwindcss": "^4.3.3",
131
127
  "vite": "^7.3.1",
@@ -136,23 +132,17 @@
136
132
  "peerDependencies": {
137
133
  "@floating-ui/react": "^0.27.0",
138
134
  "@graphql-typed-document-node/core": "^3.2.0",
139
- "@heroui/accordion": "^2.2.0",
140
- "@heroui/badge": "^2.2.0",
141
- "@heroui/pagination": "^2.2.0",
142
- "@heroui/system": "^2.4.18",
143
- "@heroui/theme": "^2.4.24",
144
- "@heroui/tooltip": "^2.2.0",
145
135
  "@types/react": "^18.0.0",
146
136
  "framer-motion": "^11.18.0",
147
- "graphql-request": "^6.1.0",
137
+ "graphql": "^16.6.0",
148
138
  "next": "^14.0.0",
149
139
  "react": "^18.0.0",
150
- "react-aria": "^3.46.0",
151
- "react-aria-components": "^1.15.0",
140
+ "react-aria": "^3.48.0",
141
+ "react-aria-components": "^1.17.0",
152
142
  "react-dom": "^18.0.0",
153
143
  "react-hook-form": "^7.71.1",
154
144
  "react-international-phone": "^4.3.0",
155
- "react-stately": "^3.36.0"
145
+ "react-stately": "^3.46.0"
156
146
  },
157
147
  "peerDependenciesMeta": {
158
148
  "@floating-ui/react": {
@@ -161,28 +151,10 @@
161
151
  "@graphql-typed-document-node/core": {
162
152
  "optional": true
163
153
  },
164
- "@heroui/accordion": {
165
- "optional": true
166
- },
167
- "@heroui/badge": {
168
- "optional": true
169
- },
170
- "@heroui/pagination": {
171
- "optional": true
172
- },
173
- "@heroui/system": {
174
- "optional": true
175
- },
176
- "@heroui/theme": {
177
- "optional": true
178
- },
179
- "@heroui/tooltip": {
180
- "optional": true
181
- },
182
154
  "framer-motion": {
183
155
  "optional": true
184
156
  },
185
- "graphql-request": {
157
+ "graphql": {
186
158
  "optional": true
187
159
  },
188
160
  "next": {
@@ -1,11 +1,100 @@
1
- import { ClientError, RequestDocument, Variables } from 'graphql-request';
2
- import { GraphQLClientRequestHeaders } from 'graphql-request/build/esm/types';
3
- import { TypedDocumentNode } from '@graphql-typed-document-node/core';
4
- export { ClientError, GraphQLClient } from 'graphql-request';
1
+ /**
2
+ * Auth-aware GraphQL client over plain `fetch` and string documents.
3
+ *
4
+ * Centralizes four behaviors that consumer apps would otherwise re-implement
5
+ * in every actor / mutation handler:
6
+ *
7
+ * 1. **401 → dispatch expired-session event.** Lets the app's auth machine
8
+ * log the user out, toast "session expired", and redirect to login.
9
+ * 2. **403 → dispatch permission-denied event.**
10
+ * 3. **5xx → dispatch server-error event.**
11
+ * 4. **Partial responses (HTTP 200 with both `data` and `errors`).** The
12
+ * `graphqlRequest()` form returns the partial `data` rather than
13
+ * throwing — single field errors don't tear down the whole UI.
14
+ *
15
+ * CTAP-1383: this used to wrap `graphql-request`, whose static imports put
16
+ * the whole `graphql` package (parser, printer, AST) plus `cross-fetch` in
17
+ * every page's initial JS. Documents are codegen'd as `TypedDocumentString`
18
+ * now (`documentMode: 'string'`), so executing a request is one JSON POST —
19
+ * no GraphQL runtime needed. `ClientError` is our own class with the same
20
+ * shape `graphql-request` threw (`response.data/errors/status`,
21
+ * `request.query/variables`), so downstream handling — including the
22
+ * identity-agnostic `getRequestErrorStatus` from `@clicktap/ui/utils/error`
23
+ * — is unchanged. Owning the class also ends the dual-package hazard the
24
+ * old re-export existed to paper over.
25
+ *
26
+ * Decoupled from app runtime context (xstate actors, routers, toast systems)
27
+ * via an event bus. The wrappers dispatch typed events on the bus; the app
28
+ * subscribes from a Provider component where actors and router are in scope.
29
+ *
30
+ * Two entry points:
31
+ *
32
+ * - `createAuthAwareGraphqlClient()` — returns a `request(url, doc, …)`
33
+ * callable. Drop-in for SSR or simple call sites that just need
34
+ * "fetch with the right headers and bail on errors".
35
+ *
36
+ * - `createAuthAwareFetch()` — returns a `fetch` function that dispatches
37
+ * events on 401/403/5xx. For callers that manage their own request
38
+ * shape (form-data uploads, per-call cors/credentials config). The
39
+ * wrapper sees the raw HTTP response, so it catches all status codes
40
+ * regardless of how the request was built.
41
+ *
42
+ * Subscribe to events via the shared bus:
43
+ *
44
+ * import { authAwareEvents } from '@clicktap/ui/utils/createAuthAwareGraphqlClient';
45
+ * const unsub = authAwareEvents.subscribe('unauthenticated', ({ status }) => { … });
46
+ */
47
+ /**
48
+ * A GraphQL document as executable text. Structurally matches codegen's
49
+ * `TypedDocumentString` (`documentMode: 'string'`): a `String` subclass
50
+ * carrying the result/variables types, so response typing flows through
51
+ * call sites exactly as it did with `TypedDocumentNode`. Plain strings are
52
+ * accepted for untyped/ad-hoc calls.
53
+ *
54
+ * Method syntax on the marker on purpose: method members compare
55
+ * bivariantly under `strictFunctionTypes`, so a document typed with
56
+ * specific variables (`TypedDocumentString<Q, Exact<{…}>>`) stays
57
+ * assignable at call sites that name only the result generic and let
58
+ * `TVariables` default.
59
+ */
60
+ interface DocumentTypeMarker<TData, TVariables> {
61
+ __apiType?(variables: TVariables): TData;
62
+ }
63
+ export type GraphqlDocument<TData, TVariables> = (DocumentTypeMarker<TData, TVariables> & {
64
+ toString(): string;
65
+ }) | string;
66
+ export type GraphqlVariables = Record<string, unknown>;
67
+ export interface GraphqlErrorItem {
68
+ message: string;
69
+ path?: Array<string | number>;
70
+ locations?: Array<{
71
+ line: number;
72
+ column: number;
73
+ }>;
74
+ extensions?: Record<string, unknown>;
75
+ }
76
+ /**
77
+ * Error thrown for HTTP failures and error-only GraphQL responses. Same
78
+ * shape as `graphql-request`'s `ClientError` (which this replaces): the
79
+ * status lives at `response.status`, so structural consumers like
80
+ * `getRequestErrorStatus` keep working unchanged.
81
+ */
82
+ export declare class ClientError extends Error {
83
+ readonly response: {
84
+ status: number;
85
+ data?: any;
86
+ errors?: GraphqlErrorItem[];
87
+ };
88
+ readonly request: {
89
+ query: string;
90
+ variables?: GraphqlVariables;
91
+ };
92
+ constructor(response: ClientError['response'], request: ClientError['request']);
93
+ }
5
94
  export type AuthAwareEventName = 'unauthenticated' | 'permissionDenied' | 'serverError';
6
95
  export interface AuthAwareEventDetail {
7
96
  status: number;
8
- /** Present when the trigger came from a `graphql-request` ClientError. */
97
+ /** Present when the trigger came from a GraphQL request error. */
9
98
  error?: ClientError;
10
99
  /** Present when the trigger came from the fetch wrapper. */
11
100
  response?: Response;
@@ -22,12 +111,12 @@ declare class AuthAwareEventBus {
22
111
  * listen, regardless of which call triggered the event).
23
112
  */
24
113
  export declare const authAwareEvents: AuthAwareEventBus;
25
- export type AuthAwareGraphqlRequest = <TData, TVariables extends Variables = Variables>(url: string, document: RequestDocument | TypedDocumentNode<TData, TVariables>, variables?: TVariables, requestHeaders?: GraphQLClientRequestHeaders) => Promise<TData>;
26
- export type AuthAwareGraphqlRequestWithOptions = <TData, TVariables extends Variables = Variables>(options: {
114
+ export type AuthAwareGraphqlRequest = <TData, TVariables extends GraphqlVariables = GraphqlVariables>(url: string, document: GraphqlDocument<TData, TVariables>, variables?: TVariables, requestHeaders?: HeadersInit) => Promise<TData>;
115
+ export type AuthAwareGraphqlRequestWithOptions = <TData, TVariables extends GraphqlVariables = GraphqlVariables>(options: {
27
116
  url: string;
28
- document: RequestDocument | TypedDocumentNode<TData, TVariables>;
117
+ document: GraphqlDocument<TData, TVariables>;
29
118
  variables?: TVariables;
30
- requestHeaders?: GraphQLClientRequestHeaders;
119
+ requestHeaders?: HeadersInit;
31
120
  signal?: AbortSignal;
32
121
  }) => Promise<TData>;
33
122
  export type AuthAwareGraphqlClient = AuthAwareGraphqlRequest & {
@@ -36,14 +125,13 @@ export type AuthAwareGraphqlClient = AuthAwareGraphqlRequest & {
36
125
  export declare function createAuthAwareGraphqlClient(): AuthAwareGraphqlClient;
37
126
  /**
38
127
  * Returns a `fetch` function that delegates to the global `fetch` and
39
- * dispatches `authAwareEvents` on 401/403/5xx responses. Pass to
40
- * `new GraphQLClient(url, { fetch: createAuthAwareFetch() })` from an
41
- * actor that needs custom client config.
128
+ * dispatches `authAwareEvents` on 401/403/5xx responses. For callers that
129
+ * build their own request (form-data uploads, per-call cors/credentials).
42
130
  *
43
131
  * The wrapped fetch returns the original Response untouched on success
44
- * AND on error — `graphql-request` still parses, throws, etc. exactly
45
- * as it would have. The event dispatch is a side channel for the app
46
- * to react to the auth state without coupling actor code to router /
47
- * toast actors.
132
+ * AND on error — the caller still parses, throws, etc. exactly as it
133
+ * would have. The event dispatch is a side channel for the app to react
134
+ * to the auth state without coupling caller code to router / toast actors.
48
135
  */
49
136
  export declare function createAuthAwareFetch(): typeof fetch;
137
+ export {};