jattac.libs.web.zest-button 1.2.9 → 1.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/ZestButton.d.ts +19 -2
- package/dist/ZestDropdownMenu.d.ts +16 -0
- package/dist/ZestDropdownMenuItem.d.ts +9 -0
- package/dist/index.cjs.js +163 -8
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.d.ts +19 -2
- package/dist/index.esm.js +143 -9
- package/dist/index.esm.js.map +1 -1
- package/docs/e2e/critical-paths.md +75 -0
- package/docs/features/zest-button-dropdown-options/BRS.md +213 -0
- package/docs/guidelines/AI_ARCHITECTURE.md +455 -0
- package/docs/guidelines/AI_BRS.md +435 -0
- package/docs/guidelines/AI_CODE_REVIEW.md +198 -0
- package/docs/guidelines/AI_E2E_TESTING.md +227 -0
- package/docs/guidelines/AI_GIT_WORKFLOW.md +595 -0
- package/docs/guidelines/AI_KNOWLEDGE.md +213 -0
- package/docs/guidelines/AI_PITFALLS.md +245 -0
- package/docs/guidelines/AI_STYLE_GUIDE.md +162 -0
- package/docs/guidelines/AI_TESTING.md +275 -0
- package/docs/guidelines/AI_TEST_CONFIGURATION.md +529 -0
- package/docs/guidelines/AI_WORKFLOW.md +442 -0
- package/docs/guidelines/AI_WORKFLOW_TRIGGERS.md +222 -0
- package/docs/guidelines/ai-knowledge.md +154 -0
- package/docs/guidelines/decision-log.md +149 -0
- package/package.json +78 -69
- package/dist/ZestContext.d.ts +0 -9
- package/dist/ZestProvider.d.ts +0 -8
- package/dist/semanticTypeDefaults.d.ts +0 -4
- package/docs/api.md +0 -140
- package/docs/breaking-changes.md +0 -143
- package/docs/configuration.md +0 -214
- package/docs/development.md +0 -93
- package/docs/examples.md +0 -227
- package/docs/features.md +0 -126
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# Critical Paths
|
|
2
|
+
|
|
3
|
+
This document defines the critical business paths that MUST be covered by e2E tests.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## How to Identify Critical Paths
|
|
8
|
+
|
|
9
|
+
A path is critical if:
|
|
10
|
+
|
|
11
|
+
• It handles money or payments
|
|
12
|
+
|
|
13
|
+
• It manages user authentication or authorization
|
|
14
|
+
|
|
15
|
+
• It writes to the database in ways that affect business data
|
|
16
|
+
|
|
17
|
+
• It integrates with external systems
|
|
18
|
+
|
|
19
|
+
• If broken, it causes immediate user-facing impact
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## Paths
|
|
24
|
+
|
|
25
|
+
### Authentication
|
|
26
|
+
|
|
27
|
+
- [ ] Login with valid credentials → 200 + token
|
|
28
|
+
|
|
29
|
+
- [ ] Login with invalid credentials → 401
|
|
30
|
+
|
|
31
|
+
- [ ] Token refresh → 200 + new token
|
|
32
|
+
|
|
33
|
+
- [ ] Logout → 200 + token invalidated
|
|
34
|
+
|
|
35
|
+
### Payment
|
|
36
|
+
|
|
37
|
+
- [ ] Create payment → 201 + payment record
|
|
38
|
+
|
|
39
|
+
- [ ] Confirm payment → 200 + status updated
|
|
40
|
+
|
|
41
|
+
- [ ] Handle payment failure → 200 + status updated
|
|
42
|
+
|
|
43
|
+
- [ ] Process refund → 200 + refund record
|
|
44
|
+
|
|
45
|
+
### Order
|
|
46
|
+
|
|
47
|
+
- [ ] Create order → 201 + order record
|
|
48
|
+
|
|
49
|
+
- [ ] Pay order → 200 + order status updated
|
|
50
|
+
|
|
51
|
+
- [ ] Ship order → 200 + order status updated
|
|
52
|
+
|
|
53
|
+
- [ ] Complete order → 200 + order status updated
|
|
54
|
+
|
|
55
|
+
- [ ] Cancel order → 200 + order status updated
|
|
56
|
+
|
|
57
|
+
### Data
|
|
58
|
+
|
|
59
|
+
- [ ] Import data → 200 + records created
|
|
60
|
+
|
|
61
|
+
- [ ] Export data → 200 + file returned
|
|
62
|
+
|
|
63
|
+
---
|
|
64
|
+
|
|
65
|
+
## Adding New Paths
|
|
66
|
+
|
|
67
|
+
When adding a new critical path:
|
|
68
|
+
|
|
69
|
+
1. Add the path description above
|
|
70
|
+
|
|
71
|
+
2. List each scenario with expected outcome
|
|
72
|
+
|
|
73
|
+
3. Write e2e tests covering each scenario
|
|
74
|
+
|
|
75
|
+
4. Mark scenarios as tested: `- [x]`
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
# BRS — ZestButton dropdown options (split button)
|
|
2
|
+
|
|
3
|
+
## Metadata
|
|
4
|
+
|
|
5
|
+
| Field | Value |
|
|
6
|
+
|-------|-------|
|
|
7
|
+
| Status | Complete |
|
|
8
|
+
| Task Type | Feature |
|
|
9
|
+
| Author | Claude (collaborating with repo owner) |
|
|
10
|
+
| Created | 2026-07-28 |
|
|
11
|
+
| Last Updated | 2026-07-28 |
|
|
12
|
+
| Approved | Yes — user approved full scope as drafted, 2026-07-28 |
|
|
13
|
+
|
|
14
|
+
### Implementation notes
|
|
15
|
+
|
|
16
|
+
See `test-reports/CHANGE_MANIFEST_dropdown-options.md` for the full file list, four implementation deviations from this document's original Technical Design (each empirically discovered, not guessed), and the coverage-regression fix. All 9 acceptance criteria satisfied; 81/81 tests pass; coverage 97.21% (up from the prior 95.38% baseline).
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## Problem Statement
|
|
21
|
+
|
|
22
|
+
**As a** developer using ZestButton, **I want** a way to attach a set of optional secondary actions to a button that already has one obvious default action, **so that** the button visually communicates "there's more here" and gives users a dedicated, discoverable way to reach those alternatives without cluttering the UI with several separate buttons.
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
## Business Value / Impact
|
|
27
|
+
|
|
28
|
+
Several places in Jattac products need "primary action + a few situational alternatives" (e.g. Save / Save & Close / Save As, Export / Export as CSV / Export as PDF). Today that either forces multiple separate buttons or a bespoke one-off menu per screen. A first-class ZestButton capability makes this consistent, accessible, and reusable everywhere ZestButton already is.
|
|
29
|
+
|
|
30
|
+
---
|
|
31
|
+
|
|
32
|
+
## Acceptance Criteria
|
|
33
|
+
|
|
34
|
+
### 1: Split-button rendering
|
|
35
|
+
|
|
36
|
+
- **Given** `zest.dropdownOptions` is a non-empty array
|
|
37
|
+
- **When** `ZestButton` renders
|
|
38
|
+
- **Then** it renders as two visually-fused segments sharing one pill shape: the existing button content/behavior on the left, and a chevron ("more options") segment on the right, separated by a subtle divider
|
|
39
|
+
- **And** when `dropdownOptions` is absent or empty, `ZestButton` renders exactly as it does today — zero visual or behavioral change (this is the core regression constraint for every other AC)
|
|
40
|
+
|
|
41
|
+
### 2: Main segment always fires the default action directly
|
|
42
|
+
|
|
43
|
+
- **Given** the split-button variant is rendered
|
|
44
|
+
- **When** the user clicks/taps/keyboard-activates the main (left) segment
|
|
45
|
+
- **Then** it behaves exactly like today's `ZestButton` `onClick` — no menu involvement, no extra click required. The dropdown never intercepts or delays the primary action.
|
|
46
|
+
|
|
47
|
+
### 3: Chevron segment opens/closes the menu
|
|
48
|
+
|
|
49
|
+
- **Given** the split-button variant is rendered
|
|
50
|
+
- **When** the user clicks/taps/keyboard-activates (Enter/Space) the chevron segment
|
|
51
|
+
- **Then** a menu listing `dropdownOptions` opens, positioned relative to the button with automatic collision/flip handling (opens upward instead of downward, or shifts horizontally, when there's insufficient viewport space) — provided by `@radix-ui/react-dropdown-menu`, not hand-rolled
|
|
52
|
+
- **And** clicking the chevron again, pressing Escape, or clicking/tapping outside closes it
|
|
53
|
+
- **And** the chevron rotates/reflects open vs. closed state visually (CSS-only transition, no animation library)
|
|
54
|
+
|
|
55
|
+
### 4: Each menu item is independent but DRY
|
|
56
|
+
|
|
57
|
+
- **Given** a `dropdownOptions` entry with its own `busyOptions`/`successOptions`/`confirmOptions`
|
|
58
|
+
- **When** that item is activated
|
|
59
|
+
- **Then** it goes through the *same* `useBusyState`/`useConfirmation` hooks the main button already uses — each item is its own hook instance (via a new internal `ZestDropdownMenuItem` sub-component, since React's rules of hooks forbid calling hooks in a loop at the parent level), not a re-implementation of busy/confirm logic
|
|
60
|
+
- **And** an item with no `busyOptions`/`confirmOptions` behaves like a plain synchronous menu action: selecting it calls `onClick` and closes the menu immediately (today's Radix default)
|
|
61
|
+
- **And** an item with `busyOptions.handleInternally` (default `true`, matching the main button's existing default) keeps the menu open on that row, shows the same spinner/checkmark/shake feedback the main button uses, then closes automatically once the success/fail state settles
|
|
62
|
+
- **And** an item with `confirmOptions` keeps the menu open through the full "Confirm X (5s)" countdown flow (identical semantics to the main button's confirm flow today), only closing once the confirmed action actually runs or the countdown expires
|
|
63
|
+
|
|
64
|
+
### 5: Full accessibility
|
|
65
|
+
|
|
66
|
+
- **Given** the split-button variant
|
|
67
|
+
- **Then** the chevron trigger has `aria-haspopup="menu"`, `aria-expanded`, and an accessible name (`zest.dropdownAriaLabel`, default `"More options"`, always overridable since it has no visible text)
|
|
68
|
+
- **And** the menu uses proper `role="menu"`/`role="menuitem"` semantics (via Radix, not authored by hand)
|
|
69
|
+
- **And** keyboard interaction works fully: Tab reaches the chevron as a separate stop after the main button, Enter/Space opens it, Arrow Up/Down moves between items, Escape closes and returns focus to the chevron
|
|
70
|
+
- **And** while the menu is open, the existing `isDefault` document-level Enter-key listener (`UI/ZestButton.tsx`'s `useEffect` that clicks `buttonRef` on Enter) is suppressed — Enter while the menu is open must operate on the highlighted menu item (Radix's own behavior), not re-trigger the main action underneath it
|
|
71
|
+
|
|
72
|
+
### 6: Mobile-first, desktop-aware
|
|
73
|
+
|
|
74
|
+
- **Given** the split-button variant on a touch viewport
|
|
75
|
+
- **Then** the chevron segment meets a minimum 44×44px touch target (WCAG 2.5.5 / matches the intent of the prior "improve ZestButton mobile hit area" work), even at `size="sm"`
|
|
76
|
+
- **And** on desktop/pointer input, the chevron segment is visually proportionate to the button's `size` (`sm`/`md`/`lg`) rather than always occupying the mobile-minimum footprint
|
|
77
|
+
|
|
78
|
+
### 7: Visual integration with the existing variant system
|
|
79
|
+
|
|
80
|
+
- **Given** any combination of `visualOptions.variant` (`standard`/`success`/`danger`), `size` (`sm`/`md`/`lg`), `buttonStyle` (`solid`/`outline`/`text`/`dashed`), and `theme` (`light`/`dark`/`system`)
|
|
81
|
+
- **Then** the chevron segment and the menu itself render consistently themed — no hardcoded colors independent of the variant/theme system (this is the concrete gap identified in `jattac.Libs.Web.OverflowMenu`, which hardcodes teal via Framer Motion inline styles that can't be overridden; this feature must not repeat that mistake)
|
|
82
|
+
|
|
83
|
+
### 8: Disabled/busy propagation across the whole control
|
|
84
|
+
|
|
85
|
+
- **Given** the split-button variant
|
|
86
|
+
- **When** the main segment is busy (internally-handled async click in flight) OR any menu item is busy
|
|
87
|
+
- **Then** the *entire* control (main segment + chevron) is disabled — no firing a second action from either segment while one is already in flight
|
|
88
|
+
- **And** the existing `disabled` prop, when explicitly passed, disables both segments as it does today for the single button
|
|
89
|
+
|
|
90
|
+
### 9: New dependency
|
|
91
|
+
|
|
92
|
+
- **Given** this feature requires menu positioning/dismiss/a11y mechanics
|
|
93
|
+
- **Then** `@radix-ui/react-dropdown-menu` is added as a `peerDependency` + `devDependency`, and added to both `external` arrays in `rollup.config.mjs` — following the exact precedent already established for `react-icons` in this repo, so it's never bundled into `dist/` and consumers control their own version
|
|
94
|
+
- **And** no other new dependency is introduced (no Framer Motion, no `@radix-ui/react-popover` — this repo's existing CSS-module + hooks approach covers everything else)
|
|
95
|
+
|
|
96
|
+
---
|
|
97
|
+
|
|
98
|
+
## Out of Scope
|
|
99
|
+
|
|
100
|
+
- Nested/submenus (dropdown items are a flat list; `jattac.Libs.Web.OverflowMenu` supports nesting, this feature does not need to and will not)
|
|
101
|
+
- Any change to `jattac.Libs.Web.OverflowMenu` itself, or reusing it as a dependency (evaluated and rejected — see decision log)
|
|
102
|
+
- A "menu-only" button mode where the main segment doesn't have its own default action — every split button has a real default action per the user's stated requirement
|
|
103
|
+
- Reordering/drag-and-drop of menu items
|
|
104
|
+
- Icons-within-menu-items styling beyond what `semanticType`/`icon` already provide on the main button today
|
|
105
|
+
- Any change to `ZestButton`'s existing (non-dropdown) behavior — AC 1's "zero change when `dropdownOptions` is absent" is the hard boundary
|
|
106
|
+
|
|
107
|
+
---
|
|
108
|
+
|
|
109
|
+
## Technical Design
|
|
110
|
+
|
|
111
|
+
### New dependency
|
|
112
|
+
|
|
113
|
+
| Package | Type | Purpose |
|
|
114
|
+
|---|---|---|
|
|
115
|
+
| `@radix-ui/react-dropdown-menu` | `peerDependency` + `devDependency`, externalized in `rollup.config.mjs` | Menu positioning (collision/flip), dismiss-on-outside-click/Escape, WAI-ARIA menu semantics, keyboard nav — used directly (not via `jattac.Libs.Web.OverflowMenu`) |
|
|
116
|
+
|
|
117
|
+
### New types (`UI/ZestButton.tsx`, additive)
|
|
118
|
+
|
|
119
|
+
```ts
|
|
120
|
+
export interface ZestDropdownOption {
|
|
121
|
+
key?: string;
|
|
122
|
+
label: React.ReactNode;
|
|
123
|
+
icon?: React.ReactNode;
|
|
124
|
+
disabled?: boolean;
|
|
125
|
+
semanticType?: SemanticType;
|
|
126
|
+
onClick?: (e: Event) => void | Promise<void>;
|
|
127
|
+
busyOptions?: BusyOptions;
|
|
128
|
+
successOptions?: SuccessOptions;
|
|
129
|
+
confirmOptions?: ConfirmOptions;
|
|
130
|
+
}
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
`ZestCustomProps` gains two new optional fields: `dropdownOptions?: ZestDropdownOption[]` and `dropdownAriaLabel?: string` (default `"More options"`).
|
|
134
|
+
|
|
135
|
+
### New files
|
|
136
|
+
|
|
137
|
+
```
|
|
138
|
+
UI/ZestDropdownTrigger.tsx — the chevron segment; wraps Radix's DropdownMenu.Trigger (asChild) around ZestButton's own themed button markup
|
|
139
|
+
UI/ZestDropdownMenuItem.tsx — one menu row; internally calls useBusyState/useConfirmation exactly like ZestButton.tsx does today (AC 4's DRY-via-shared-hooks requirement)
|
|
140
|
+
UI/hooks/useDropdownDisclosure.ts — thin wrapper around Radix's open/onOpenChange state, exposing the open flag ZestButton.tsx needs to suppress the isDefault Enter-key effect (AC 5)
|
|
141
|
+
Styles/ZestButton.module.css — extended (not replaced) with chevron/menu/divider classes, matching this repo's existing CSS-module + identity-obj-proxy-testable approach
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
### Modified files
|
|
145
|
+
|
|
146
|
+
- `UI/ZestButton.tsx` — additive only: renders the chevron trigger + menu when `dropdownOptions` is present; the `isDefault` `useEffect` gains the open-menu guard from AC 5. No existing branch of the current render/click logic changes when `dropdownOptions` is absent (AC 1).
|
|
147
|
+
- `package.json` — new peer/dev dependency.
|
|
148
|
+
- `rollup.config.mjs` — `@radix-ui/react-dropdown-menu` added to both `external` arrays (JS bundle config and the `.d.ts` bundle config), matching the existing `react`/`react-dom`/`react-icons` entries exactly.
|
|
149
|
+
|
|
150
|
+
### Pattern precedent
|
|
151
|
+
|
|
152
|
+
`jattac.Libs.Web.OverflowMenu` (sibling repo, `D:\work\nyingi\code\systems\jattac-web-libs\jattac.Libs.Web.OverflowMenu`) already depends on `@radix-ui/react-dropdown-menu` in production — this is a proven, already-trusted-in-this-org package, just consumed directly instead of through that repo's opinionated (non-themeable) wrapper.
|
|
153
|
+
|
|
154
|
+
---
|
|
155
|
+
|
|
156
|
+
## Edge Cases
|
|
157
|
+
|
|
158
|
+
### Menu item busy while main button is also mid-confirmation
|
|
159
|
+
**Scenario:** User opens the menu while the main button's own `confirmOptions` countdown is active.
|
|
160
|
+
**Expected:** AC 8 disables the whole control while *busy*, but a pending confirm countdown on the main segment is not "busy" (mirrors today's single-button semantics, where `wasSuccessful`/`wasFailed`/`internalBusy` — not `awaitingConfirm` — drive `isDisabled`). The menu remains openable during a pending confirm; opening it does not cancel the main segment's countdown.
|
|
161
|
+
|
|
162
|
+
### Zero dropdownOptions after filtering
|
|
163
|
+
**Scenario:** Caller passes `dropdownOptions: []` (explicit empty array, not `undefined`).
|
|
164
|
+
**Expected:** Same as AC 1's "absent" case — no chevron segment rendered. An empty array is not treated differently from `undefined`.
|
|
165
|
+
|
|
166
|
+
### Rapid open/close while a menu item is busy
|
|
167
|
+
**Scenario:** A menu item's async `onClick` is in flight (menu held open by AC 4); user presses Escape or clicks outside.
|
|
168
|
+
**Expected:** The menu closes on dismissal regardless of the in-flight promise — the promise still runs to completion and still updates that item's busy/success/fail state internally, it's just not visible until/unless the menu is reopened. No dangling state or memory leak (existing `useBusyState`/`useConfirmation` cleanup-on-unmount behavior, already tested in `__tests__/hooks/`, covers this as long as the item component doesn't literally unmount when the menu closes — confirm during implementation whether Radix unmounts closed menu content by default, and force-mount if so, so item state survives close/reopen within one page session).
|
|
169
|
+
|
|
170
|
+
---
|
|
171
|
+
|
|
172
|
+
## Dependencies
|
|
173
|
+
|
|
174
|
+
### Internal
|
|
175
|
+
- Builds on the existing `useBusyState`/`useConfirmation`/`useZestConfig` hooks and `Styles/ZestButton.module.css` conventions — no changes required to any of them.
|
|
176
|
+
|
|
177
|
+
### External
|
|
178
|
+
- `@radix-ui/react-dropdown-menu` (new, see Technical Design).
|
|
179
|
+
|
|
180
|
+
---
|
|
181
|
+
|
|
182
|
+
## Risks and Open Questions
|
|
183
|
+
|
|
184
|
+
### Risks
|
|
185
|
+
- Radix's `DropdownMenu.Content` renders into a portal by default — needs a sensible default portal target (`document.body`) with an optional override prop for consumers using shadow DOM/iframes, otherwise CSS custom properties (theme forcing) that rely on ancestor selectors may not reach the portaled menu. Needs verifying during implementation, not just assumed.
|
|
186
|
+
- `@radix-ui/react-dropdown-menu`'s peer range for React needs checking against this repo's `react: ">=16.8.0"` peer floor before finalizing the `package.json` entry — if Radix's floor is higher (e.g. requires React 16.8+ hooks, which is fine, but some Radix packages have crept their stated minimums up in recent majors), the two peer ranges must be reconciled or documented as a floor bump.
|
|
187
|
+
|
|
188
|
+
### Open Questions
|
|
189
|
+
- None blocking — flagged risks above will be resolved empirically during implementation (installed, checked, and reported), not guessed.
|
|
190
|
+
|
|
191
|
+
---
|
|
192
|
+
|
|
193
|
+
## Regression Prevention
|
|
194
|
+
|
|
195
|
+
### For Features
|
|
196
|
+
|
|
197
|
+
**New Tests Required:**
|
|
198
|
+
- Every AC above (1–9) gets at least one behavioral test in `__tests__/ZestButton.test.tsx` or a new `__tests__/ZestDropdownMenuItem.test.tsx` / `__tests__/hooks/useDropdownDisclosure.test.ts`, following this repo's existing test conventions (real assertions, fake timers for confirm/busy timing, `identity-obj-proxy` for CSS modules).
|
|
199
|
+
- A dedicated regression test asserting AC 1's "zero behavior change when `dropdownOptions` is absent" — re-running (not duplicating) a representative slice of the existing 52-test suite against a build with the new prop wired in but unused.
|
|
200
|
+
|
|
201
|
+
---
|
|
202
|
+
|
|
203
|
+
## Definition of Done
|
|
204
|
+
|
|
205
|
+
- [ ] BRS approved by user
|
|
206
|
+
- [ ] All acceptance criteria pass (new tests green, full existing 52-test suite still green)
|
|
207
|
+
- [ ] `npm run build` clean (no new TS diagnostics, no new bundle-size surprises from an accidentally-inlined Radix dependency — verify `external` actually excluded it)
|
|
208
|
+
- [ ] No coverage regression from the current baseline (`test-reports/coverage-baseline.json`)
|
|
209
|
+
- [ ] No unsolicited changes outside this BRS's scope
|
|
210
|
+
- [ ] Code matches existing patterns (hooks-per-concern, CSS modules, `identity-obj-proxy`-testable styling)
|
|
211
|
+
- [ ] Change manifest produced
|
|
212
|
+
- [ ] Self review passed with all PASS items
|
|
213
|
+
- [ ] `docs/guidelines/ai-knowledge.md` and `docs/guidelines/decision-log.md` updated
|