@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.
- package/components/Accordion/Accordion.d.ts +76 -2
- package/components/Accordion/Accordion.js +1 -1
- package/components/Accordion/index.d.ts +2 -3
- package/components/Accordion/index.js +1 -1
- package/components/Badge/Badge.js +1 -1
- package/components/Badge/Badge.types.d.ts +27 -2
- package/components/Badge/index.js +1 -1
- package/components/Collapsible/Collapsible.js +1 -1
- package/components/Collection/CollectionFilters/CollectionFilters.js +1 -1
- package/components/Collection/CollectionFilters/index.js +1 -1
- package/components/Collection/CollectionToolbar/CollectionToolbar.js +1 -1
- package/components/Collection/ProductCardSwatches/ProductCardSwatches.js +1 -1
- package/components/Collection/index.js +1 -1
- package/components/ContextMenu/ContextMenu.js +1 -1
- package/components/DialogTrigger/DialogTrigger.d.ts +18 -10
- package/components/DialogTrigger/DialogTrigger.js +1 -1
- package/components/DialogTrigger/index.js +1 -1
- package/components/Drawer/Drawer.js +1 -1
- package/components/Menu/Menu.js +1 -1
- package/components/Meter/Meter.js +1 -1
- package/components/NumberTicker/DigitResolver.js +1 -1
- package/components/NumberTicker/NumberTicker.js +1 -1
- package/components/Pagination/Pagination.d.ts +27 -2
- package/components/Pagination/Pagination.js +1 -1
- package/components/Pagination/index.js +1 -1
- package/components/Progressbar/LinearProgressbar.js +1 -1
- package/components/Tabs/Tab.js +1 -1
- package/components/Tooltip/Tooltip.d.ts +9 -1
- package/components/Tooltip/Tooltip.js +1 -1
- package/components/Tooltip/Tooltip.types.d.ts +21 -2
- package/components/Tooltip/index.js +1 -1
- package/components/UploadImage/UploadImage.js +1 -1
- package/docs/migrations/2026/08/ui-2026-08-21-graphql-client-string-documents.md +45 -0
- package/docs/migrations/2026/08/ui-2026-08-21-graphql-peer-covered.md +26 -0
- package/docs/migrations/2026/08/ui-2026-08-21-heroui-removed.md +54 -0
- package/docs/migrations/2026/08/ui-2026-08-21-lazymotion-m-components.md +54 -0
- package/docs/migrations/2026/08/ui-2026-08-21-modal-triggers-wired-explicitly.md +43 -0
- package/docs/migrations/2026/08/ui-2026-08-21-next-image-fallback-keeps-fill.md +38 -0
- package/docs/migrations/2026/08/ui-2026-08-22-accordion-divider-between-items.md +30 -0
- package/docs/migrations/2026/08/ui-2026-08-22-accordion-divider-class-ownership.md +14 -0
- package/docs/migrations/2026/08/ui-2026-08-22-collection-accessibility-fixes.md +28 -0
- package/docs/migrations/2026/08/ui-2026-08-22-react-aria-moves-as-one-version-set.md +46 -0
- package/docs/migrations/index.json +101 -0
- package/next/Image.js +1 -1
- package/package.json +10 -38
- package/utils/createAuthAwareGraphqlClient.d.ts +104 -16
- 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
|
|
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": "
|
|
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
|
|
116
|
+
"graphql": "16.12.0",
|
|
121
117
|
"next": "14.2.35",
|
|
122
118
|
"react": "18.3.1",
|
|
123
|
-
"react-aria": "3.
|
|
124
|
-
"react-aria-components": "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.
|
|
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
|
|
137
|
+
"graphql": "^16.6.0",
|
|
148
138
|
"next": "^14.0.0",
|
|
149
139
|
"react": "^18.0.0",
|
|
150
|
-
"react-aria": "^3.
|
|
151
|
-
"react-aria-components": "^1.
|
|
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.
|
|
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
|
|
157
|
+
"graphql": {
|
|
186
158
|
"optional": true
|
|
187
159
|
},
|
|
188
160
|
"next": {
|
|
@@ -1,11 +1,100 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
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
|
|
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
|
|
26
|
-
export type AuthAwareGraphqlRequestWithOptions = <TData, TVariables extends
|
|
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:
|
|
117
|
+
document: GraphqlDocument<TData, TVariables>;
|
|
29
118
|
variables?: TVariables;
|
|
30
|
-
requestHeaders?:
|
|
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.
|
|
40
|
-
*
|
|
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 —
|
|
45
|
-
*
|
|
46
|
-
* to
|
|
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 {};
|