pixelize-design-library 2.3.1-beta.19 → 2.3.1-beta.20
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/.cursor/TASK-SETUP.md +43 -0
- package/.cursor/agents/be-impl.md +37 -0
- package/.cursor/agents/fe-impl.md +39 -0
- package/.cursor/agents/task-plan.md +56 -0
- package/.cursor/agents/test-create.md +31 -0
- package/.cursor/agents/test-exec.md +26 -0
- package/.cursor/hooks/task-hint.env +1 -0
- package/.cursor/hooks/task-skill-nudge.sh +71 -0
- package/.cursor/hooks/task-slash-guard.sh +31 -0
- package/.cursor/hooks.json +13 -0
- package/.cursor/modules/account-management/MODULE.md +16 -0
- package/.cursor/modules/buttons/MODULE.md +13 -0
- package/.cursor/modules/cards/MODULE.md +13 -0
- package/.cursor/modules/charts/MODULE.md +13 -0
- package/.cursor/modules/common/MODULE.md +13 -0
- package/.cursor/modules/contact-auth/MODULE.md +13 -0
- package/.cursor/modules/data-display/MODULE.md +13 -0
- package/.cursor/modules/feedback/MODULE.md +14 -0
- package/.cursor/modules/form/MODULE.md +13 -0
- package/.cursor/modules/inputs-basic/MODULE.md +13 -0
- package/.cursor/modules/inputs-date-file/MODULE.md +19 -0
- package/.cursor/modules/inputs-select/MODULE.md +14 -0
- package/.cursor/modules/inputs-toggle/MODULE.md +13 -0
- package/.cursor/modules/kanban/MODULE.md +14 -0
- package/.cursor/modules/layout-navigation/MODULE.md +14 -0
- package/.cursor/modules/overlays/MODULE.md +13 -0
- package/.cursor/modules/playground/MODULE.md +15 -0
- package/.cursor/modules/table/MODULE.md +15 -0
- package/.cursor/modules/theme/MODULE.md +15 -0
- package/.cursor/modules/types-exports/MODULE.md +17 -0
- package/.cursor/modules/utility-ui/MODULE.md +15 -0
- package/.cursor/modules/utils-hooks/MODULE.md +13 -0
- package/.cursor/pixelize-task-statusline.sh +64 -0
- package/.cursor/plans/blocked/.gitkeep +0 -0
- package/.cursor/plans/current.md +35 -0
- package/.cursor/plans/done/.gitkeep +0 -0
- package/.cursor/rules +31 -0
- package/.cursor/skills/task/SKILL.md +167 -0
- package/CLAUDE.md +122 -0
- package/dist/Components/Card/PaymentCard/PaymentCard.d.ts +1 -1
- package/dist/Components/Card/PaymentCard/PaymentCard.js +3 -3
- package/dist/Components/Card/PaymentCard/PaymentCardProps.d.ts +1 -0
- package/dist/Components/Dropdown/DropDown.js +28 -2
- package/dist/Components/Dropdown/Dropdown.test.d.ts +1 -0
- package/dist/Components/Dropdown/Dropdown.test.js +102 -0
- package/dist/Components/SideBar/components/OtherApps.test.js +3 -2
- package/dist/Components/Table/components/TableBody.virtualize.test.js +13 -3
- package/dist/Components/Table/settings/ManageColumns.test.js +1 -0
- package/dist/Theme/index.d.ts +4 -4
- package/dist/Theme/index.js +4 -4
- package/package.json +1 -1
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# Utility UI
|
|
2
|
+
|
|
3
|
+
## Purpose
|
|
4
|
+
|
|
5
|
+
Generic standalone UI helpers: CopyButton, ScrollToTop, LazyWrapper, MoreItems, EmptyState, PdfViewer, FilePreview, FieldSelectModal, ProfilePhotoViewer, StageProgress.
|
|
6
|
+
|
|
7
|
+
## Key paths
|
|
8
|
+
|
|
9
|
+
- `src/Components/CopyButton/`, `ScrollToTop/`, `LazyWrapper/`, `MoreItems/`, `EmptyState/`, `PdfViewer/`, `FilePreview/`, `FieldSelectModal/`, `ProfilePhotoViewer/`, `StageProgress/`
|
|
10
|
+
|
|
11
|
+
## Key rules
|
|
12
|
+
|
|
13
|
+
- FilePreview also exports `FilePreviewTrigger`; StageProgress also exports `StageItem` (StepperStage is internal)
|
|
14
|
+
- LazyWrapper wraps lazy/Suspense loading; ScrollToTop is a scroll-position helper — keep them framework-agnostic (props in, events out)
|
|
15
|
+
- Single-file components (CopyButton, ScrollToTop, LazyWrapper, PdfViewer, FieldSelectModal) have no colocated Props file — types are inline
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# Utils & Hooks
|
|
2
|
+
|
|
3
|
+
## Purpose
|
|
4
|
+
|
|
5
|
+
Shared utilities and hooks: debounce, table helpers, preferences.
|
|
6
|
+
|
|
7
|
+
## Key paths
|
|
8
|
+
|
|
9
|
+
- `src/Utils/table.ts`, `src/Hooks/usePreferences.ts`, `src/services/feedback.ts`
|
|
10
|
+
|
|
11
|
+
## Key rules
|
|
12
|
+
|
|
13
|
+
- `debounce` exported from index.ts
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Local status line only — never sent to the model. See .cursor/TASK-SETUP.md
|
|
3
|
+
set -euo pipefail
|
|
4
|
+
|
|
5
|
+
STATE_FILE="${HOME}/.cursor/pixelize-skill-session-state.json"
|
|
6
|
+
|
|
7
|
+
payload=$(cat)
|
|
8
|
+
dir=$(echo "$payload" | jq -r ".cwd // .workspace.current_dir // empty")
|
|
9
|
+
session_id=$(echo "$payload" | jq -r ".session_id // empty")
|
|
10
|
+
base=$(basename "$dir")
|
|
11
|
+
|
|
12
|
+
show_no_skill_warning=false
|
|
13
|
+
if [[ -n "$session_id" && -f "$STATE_FILE" ]]; then
|
|
14
|
+
if jq -e --arg id "$session_id" '.[$id].chatting_without_skill == true' "$STATE_FILE" >/dev/null 2>&1; then
|
|
15
|
+
show_no_skill_warning=true
|
|
16
|
+
fi
|
|
17
|
+
fi
|
|
18
|
+
|
|
19
|
+
case "$base" in
|
|
20
|
+
account-frontend|account-service)
|
|
21
|
+
line1="/ → task (Account FE + BE)"
|
|
22
|
+
line2=""
|
|
23
|
+
;;
|
|
24
|
+
crm-frontend|crm-service)
|
|
25
|
+
line1="/ → task (this repo)"
|
|
26
|
+
line2="/ → crm-task (CRM FE + BE)"
|
|
27
|
+
;;
|
|
28
|
+
social-frontend|social-service)
|
|
29
|
+
line1="/ → task (this repo)"
|
|
30
|
+
line2="/ → social-task (Social FE + BE)"
|
|
31
|
+
;;
|
|
32
|
+
tickets-frontend)
|
|
33
|
+
line1="/ → task (this repo)"
|
|
34
|
+
line2="/ → tickets-task"
|
|
35
|
+
;;
|
|
36
|
+
crm-mobile)
|
|
37
|
+
line1="/ → task (this repo)"
|
|
38
|
+
line2="/ → crm-mobile-task"
|
|
39
|
+
;;
|
|
40
|
+
Micro-Components)
|
|
41
|
+
line1="/ → task (this repo)"
|
|
42
|
+
line2="/ → design-task"
|
|
43
|
+
;;
|
|
44
|
+
hrms)
|
|
45
|
+
line1="/ → task (this repo)"
|
|
46
|
+
line2="/ → hrms-task (HRMS FE + BE)"
|
|
47
|
+
;;
|
|
48
|
+
*)
|
|
49
|
+
line1="Open a Pixelize repo root for / → task"
|
|
50
|
+
line2=""
|
|
51
|
+
;;
|
|
52
|
+
esac
|
|
53
|
+
|
|
54
|
+
if [[ "$show_no_skill_warning" == "true" ]]; then
|
|
55
|
+
echo -e "\033[1;33m\033[7m ⚠ CHATTING WITHOUT TASK SKILL — use / menu ⚠ \033[0m"
|
|
56
|
+
echo -e "\033[1;33m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\033[0m"
|
|
57
|
+
fi
|
|
58
|
+
|
|
59
|
+
echo -e "\033[1;33m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\033[0m"
|
|
60
|
+
echo -e "\033[1;33m FEATURE WORK → $line1\033[0m"
|
|
61
|
+
if [ -n "$line2" ]; then
|
|
62
|
+
echo -e "\033[1;33m Full pipeline → $line2\033[0m"
|
|
63
|
+
fi
|
|
64
|
+
echo -e "\033[1;33m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\033[0m"
|
|
File without changes
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# Fix Dropdown menu viewport overflow
|
|
2
|
+
|
|
3
|
+
## Goal
|
|
4
|
+
|
|
5
|
+
When the Dropdown trigger sits near a screen edge (e.g. far right), the portaled menu currently renders off-screen. It should reposition to stay fully visible within the viewport.
|
|
6
|
+
|
|
7
|
+
## Type
|
|
8
|
+
|
|
9
|
+
fix
|
|
10
|
+
|
|
11
|
+
## Scope
|
|
12
|
+
|
|
13
|
+
- In scope: `src/Components/Dropdown/DropDown.tsx` positioning logic
|
|
14
|
+
- Out of scope: visual restyle, new props API, other select components
|
|
15
|
+
- Agents needed: fe-impl
|
|
16
|
+
|
|
17
|
+
## Root cause
|
|
18
|
+
|
|
19
|
+
`updateMenuPos` sets `top = r.bottom, left = r.left` with no viewport clamping. The `position="fixed"` portaled menu overflows when the trigger is near the right/bottom edge.
|
|
20
|
+
|
|
21
|
+
## Tasks
|
|
22
|
+
|
|
23
|
+
### Component (fe-impl)
|
|
24
|
+
|
|
25
|
+
- [ ] Clamp horizontal position: right-align to trigger and clamp to viewport (8px gutter) when overflowing the right edge; clamp left edge too.
|
|
26
|
+
- [ ] Clamp vertical position: flip above the trigger when the menu would overflow the bottom and there is more space above; otherwise clamp.
|
|
27
|
+
- [ ] Measure the rendered menu (width/height) via the menu ref so clamping uses real dimensions; recompute on open, scroll, resize.
|
|
28
|
+
|
|
29
|
+
### Tests
|
|
30
|
+
|
|
31
|
+
- [ ] Jest test for Dropdown: opens, renders options, repositions left when trigger near right edge.
|
|
32
|
+
|
|
33
|
+
## Risks / assumptions
|
|
34
|
+
|
|
35
|
+
- Uses viewport (window.innerWidth/Height) — fixed positioning, correct for portal-to-body.
|
|
File without changes
|
package/.cursor/rules
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
## Session start
|
|
2
|
+
- Always read CLAUDE.md before starting any task
|
|
3
|
+
- Always read MODULE.md before touching a component family (`.cursor/modules/<domain>/MODULE.md`)
|
|
4
|
+
- Read one similar existing component before implementing
|
|
5
|
+
- Never invent a new pattern if one already exists
|
|
6
|
+
|
|
7
|
+
## Implementation
|
|
8
|
+
- Components live in `src/Components/<Name>/`
|
|
9
|
+
- Public API via `src/index.ts` only
|
|
10
|
+
- Demo pages in `src/Pages/` for playground — not published
|
|
11
|
+
- Never add CRM/app-specific API calls to library components
|
|
12
|
+
- Use theme tokens from `src/Theme/` — no hardcoded brand colors
|
|
13
|
+
- Add Jest tests for new behavior (`*.test.tsx`)
|
|
14
|
+
- Keep changes minimal — no scope creep
|
|
15
|
+
|
|
16
|
+
## Output quality
|
|
17
|
+
- Never create placeholder files unless explicitly asked
|
|
18
|
+
- Never use fake data, lorem ipsum, example.com, or demo seeds
|
|
19
|
+
- Never leave TODO comments — implement fully or report blocked
|
|
20
|
+
- Production-ready components with loading/error states where applicable
|
|
21
|
+
|
|
22
|
+
## Branch rules
|
|
23
|
+
- Always branch from **develop** — never from main
|
|
24
|
+
- Feature work: `feature/<domain>-<short-description>`
|
|
25
|
+
- Bug fixes: `fix/<domain>-<short-description>`
|
|
26
|
+
- Never commit directly to main
|
|
27
|
+
|
|
28
|
+
## Commit rules
|
|
29
|
+
- Never commit without explicit user confirmation
|
|
30
|
+
- Never run git push under any condition
|
|
31
|
+
- Never add Cursor co-author lines
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: task
|
|
3
|
+
description: >-
|
|
4
|
+
Runs the Pixelize Design Library dev pipeline: task-plan → fe-impl → test-create → test-exec → review gate → commit. Use when the user invokes @task, /task, or requests component library delivery.
|
|
5
|
+
disable-model-invocation: true
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Task Pipeline — Pixelize Design Library
|
|
9
|
+
|
|
10
|
+
Single library repo **Micro-Components** (`pixelize-design-library`). Module docs: `.cursor/modules/<domain>/MODULE.md`.
|
|
11
|
+
|
|
12
|
+
Run phases in order. Do not skip unless user opts out.
|
|
13
|
+
|
|
14
|
+
```
|
|
15
|
+
- [ ] 1. Task plan (agent: task-plan)
|
|
16
|
+
- [ ] 2. Implementation (agents: fe-impl + be-impl, parallel when both needed)
|
|
17
|
+
- [ ] 3a. Test creation (agent: test-create)
|
|
18
|
+
- [ ] 3b. Test execution (agent: test-exec)
|
|
19
|
+
- [ ] 4. Review gate (orchestrator — show diff, wait for user)
|
|
20
|
+
- [ ] 5. Commit (orchestrator only)
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## Before Starting
|
|
26
|
+
|
|
27
|
+
1. Read CLAUDE.md
|
|
28
|
+
2. Confirm user goal — feat / fix / refactor / chore
|
|
29
|
+
3. Tell user which phases apply
|
|
30
|
+
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
## Phase 1 — Task Plan
|
|
34
|
+
|
|
35
|
+
Agent: task-plan → `.cursor/agents/task-plan.md`
|
|
36
|
+
|
|
37
|
+
- Read `.cursor/modules/<domain>/MODULE.md` for touched component families
|
|
38
|
+
- Plan saved to `.cursor/plans/current.md`
|
|
39
|
+
- If current.md already exists — read it first, user may be resuming
|
|
40
|
+
|
|
41
|
+
Gate: do not start phase 2 until plan has concrete tasks.
|
|
42
|
+
|
|
43
|
+
---
|
|
44
|
+
|
|
45
|
+
## Phase 2 — Implementation
|
|
46
|
+
|
|
47
|
+
| Plan says | Action |
|
|
48
|
+
|-----------|--------|
|
|
49
|
+
| Library only | Invoke fe-impl only |
|
|
50
|
+
| Both | Rare — fe-impl + test agents |
|
|
51
|
+
|
|
52
|
+
Pass `.cursor/plans/current.md` to each agent.
|
|
53
|
+
|
|
54
|
+
If blocked mid-task:
|
|
55
|
+
- Save progress to `.cursor/plans/blocked/<task-name>.md` with exact blocker details
|
|
56
|
+
- Stop pipeline and report to user
|
|
57
|
+
|
|
58
|
+
Gate: all required agents must report Done or Blocked before phase 3.
|
|
59
|
+
|
|
60
|
+
---
|
|
61
|
+
|
|
62
|
+
## Phase 3 — Tests
|
|
63
|
+
|
|
64
|
+
3a: test-create → `.cursor/agents/test-create.md`
|
|
65
|
+
3b: test-exec → `.cursor/agents/test-exec.md`
|
|
66
|
+
|
|
67
|
+
Jest: `npm test` from repo root.
|
|
68
|
+
|
|
69
|
+
Gate: do not proceed to review if tests failed and not explicitly waived by user.
|
|
70
|
+
|
|
71
|
+
---
|
|
72
|
+
|
|
73
|
+
## Phase 4 — Review Gate
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
git status
|
|
77
|
+
git diff
|
|
78
|
+
git diff --staged
|
|
79
|
+
git log -5 --oneline
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
Run git status/diff. STOP and wait for user approval.
|
|
83
|
+
|
|
84
|
+
```
|
|
85
|
+
CHANGES READY TO COMMIT
|
|
86
|
+
|
|
87
|
+
Backend:
|
|
88
|
+
- path/to/file — what changed
|
|
89
|
+
|
|
90
|
+
Frontend:
|
|
91
|
+
- path/to/component — what changed
|
|
92
|
+
|
|
93
|
+
Tests:
|
|
94
|
+
- path/to/test — what it covers
|
|
95
|
+
|
|
96
|
+
MODULE.md updates:
|
|
97
|
+
- path/to/MODULE.md
|
|
98
|
+
|
|
99
|
+
Excluded:
|
|
100
|
+
- none
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
STOP. Wait for: "yes" / "commit" / "looks good" / "go ahead"
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
## Phase 5 — Commit
|
|
107
|
+
|
|
108
|
+
### Commit message format
|
|
109
|
+
|
|
110
|
+
```
|
|
111
|
+
<type>(<module>): <concise imperative title>
|
|
112
|
+
|
|
113
|
+
Backend:
|
|
114
|
+
- <specific change — file context>
|
|
115
|
+
|
|
116
|
+
Frontend:
|
|
117
|
+
- <specific change — file context>
|
|
118
|
+
|
|
119
|
+
Tests:
|
|
120
|
+
- <what is covered and where>
|
|
121
|
+
|
|
122
|
+
Config:
|
|
123
|
+
- <migrations, registry changes, config updates>
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
### Commit steps
|
|
127
|
+
|
|
128
|
+
1. Scan diff — if .env or secrets found, warn user and ask if they want to include. Commit only on confirmation.
|
|
129
|
+
2. git add relevant files — state any exclusions clearly
|
|
130
|
+
3. git commit using HEREDOC — never add Cursor attribution lines
|
|
131
|
+
4. If Cursor injects co-author lines — amend immediately if unpushed
|
|
132
|
+
5. Verify: git status + git log -1 --format=full
|
|
133
|
+
6. Show commit hash to user
|
|
134
|
+
7. Move `.cursor/plans/current.md` → `.cursor/plans/done/<YYYY-MM-DD>-<title>.md`
|
|
135
|
+
|
|
136
|
+
Never run git push under any condition.
|
|
137
|
+
|
|
138
|
+
### Commit safety
|
|
139
|
+
|
|
140
|
+
- Never --no-verify unless user explicitly requests
|
|
141
|
+
- Never force-push any branch
|
|
142
|
+
- Nothing staged → skip commit, report clearly
|
|
143
|
+
|
|
144
|
+
---
|
|
145
|
+
|
|
146
|
+
## Final Report
|
|
147
|
+
|
|
148
|
+
| Phase | Status | Notes |
|
|
149
|
+
| -------- | -------------- | --------------------------------- |
|
|
150
|
+
| 1 Plan | done / skipped | saved to .cursor/plans/current.md |
|
|
151
|
+
| 2 Impl | fe / be / both | done / blocked |
|
|
152
|
+
| 3 Tests | create / exec | pass / fail / blocked |
|
|
153
|
+
| 4 Review | approved | confirmed by user |
|
|
154
|
+
| 5 Commit | done / skipped | hash if committed |
|
|
155
|
+
|
|
156
|
+
---
|
|
157
|
+
|
|
158
|
+
## User Opt-outs
|
|
159
|
+
|
|
160
|
+
| User says | Effect |
|
|
161
|
+
| ------------- | ----------------------------------------- |
|
|
162
|
+
| "plan only" | Stop after phase 1 |
|
|
163
|
+
| "no tests" | Skip phase 3 — confirm with user first |
|
|
164
|
+
| "no commit" | Stop after phase 4 review |
|
|
165
|
+
| "commit only" | Phase 4+5 only — still show diff and wait |
|
|
166
|
+
| "skip review" | Go straight to commit — confirm with user |
|
|
167
|
+
|
package/CLAUDE.md
ADDED
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
# Pixelize Design Library — Agent Guide
|
|
2
|
+
|
|
3
|
+
Rules for AI assistants working in **Micro-Components** (npm: `pixelize-design-library`).
|
|
4
|
+
|
|
5
|
+
## 1. App name and purpose
|
|
6
|
+
|
|
7
|
+
**Pixelize Design Library** — shared React UI component library and theme system for all Pixelize frontends. Published to npm; local Vite playground demos components in `src/Pages/`.
|
|
8
|
+
|
|
9
|
+
## 2. Stack
|
|
10
|
+
|
|
11
|
+
| Layer | Technology | Version |
|
|
12
|
+
| ------------- | ----------------------------------- | -------------- |
|
|
13
|
+
| Framework | React + TypeScript | 18.3.1 / 5.9.3 |
|
|
14
|
+
| UI base | Chakra UI + Emotion + Framer Motion | 2.8.2 |
|
|
15
|
+
| Build | `tsc` → `dist/` + CSS copy | — |
|
|
16
|
+
| Dev | Vite playground | 6.2.0 |
|
|
17
|
+
| Styling | Tailwind + global CSS | 3.4.17 |
|
|
18
|
+
| Tests | Jest + Testing Library | 29.7.0 |
|
|
19
|
+
| Package | `pixelize-design-library` | 2.2.x |
|
|
20
|
+
|
|
21
|
+
Not a monorepo — single package, one `package.json`.
|
|
22
|
+
|
|
23
|
+
## 3. Repo relationship
|
|
24
|
+
|
|
25
|
+
| Repo / package | Role |
|
|
26
|
+
| --------------------------- | ------------------------------------------------- |
|
|
27
|
+
| **Micro-Components** (here) | Source of `pixelize-design-library` npm package |
|
|
28
|
+
| **crm-frontend** | Consumer — `npm run pixelize` to update |
|
|
29
|
+
| **account-frontend** | Consumer |
|
|
30
|
+
| **social-frontend** | Consumer |
|
|
31
|
+
| **tickets-frontend** | Consumer |
|
|
32
|
+
| **crm-mobile** | Consumer (native-base overlap — check mobile) |
|
|
33
|
+
| **pixelize-authenticator** | Companion auth UI package (separate repo) |
|
|
34
|
+
|
|
35
|
+
No backend API in this repo. Demo pages may call dev APIs for playground only.
|
|
36
|
+
|
|
37
|
+
## 4. Folder structure (2 levels)
|
|
38
|
+
|
|
39
|
+
```
|
|
40
|
+
Micro-Components/
|
|
41
|
+
├── package.json, tsconfig.json, vite.config.ts
|
|
42
|
+
├── src/
|
|
43
|
+
│ ├── index.ts public exports (~80)
|
|
44
|
+
│ ├── withTheme.tsx HOC wrapper
|
|
45
|
+
│ ├── Components/ 73 component families
|
|
46
|
+
│ ├── Theme/ 8 brand presets + useCustomTheme
|
|
47
|
+
│ ├── Pages/ 49 Vite demo/playground pages
|
|
48
|
+
│ ├── Utils/, Hooks/, services/, types/
|
|
49
|
+
│ └── App.tsx, Layout.tsx playground shell
|
|
50
|
+
├── dist/ publish output (gitignored)
|
|
51
|
+
└── .cursor/modules/ MODULE.md per component domain (22 domains)
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## 5. Dev commands
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
npm start # Vite playground
|
|
58
|
+
npm run build # tsc + copy CSS → dist/
|
|
59
|
+
npm test # Jest
|
|
60
|
+
npm run test:watch
|
|
61
|
+
npm run remove # strip dev artifacts from dist before publish
|
|
62
|
+
npm run package # version patch + build + publish
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## 6. Module / component pattern
|
|
66
|
+
|
|
67
|
+
Follow the **Table** or **KanbanBoard** pattern for complex components:
|
|
68
|
+
|
|
69
|
+
| Step | Path |
|
|
70
|
+
| ---- | ---- |
|
|
71
|
+
| Component | `src/Components/<Name>/` |
|
|
72
|
+
| Subcomponents | `src/Components/<Name>/Components/` or nested folders |
|
|
73
|
+
| Props/types | `<Name>Props.ts` or colocated types |
|
|
74
|
+
| Tests | `*.test.tsx` next to component |
|
|
75
|
+
| Stories | `*.stories.tsx` (where present) |
|
|
76
|
+
| Demo page | `src/Pages/<name>.tsx` |
|
|
77
|
+
| Public export | add to `src/index.ts` |
|
|
78
|
+
| Module doc | `.cursor/modules/<domain>/MODULE.md` |
|
|
79
|
+
|
|
80
|
+
Read `.cursor/modules/<domain>/MODULE.md` before touching a component family.
|
|
81
|
+
|
|
82
|
+
## 7. Publishing integration
|
|
83
|
+
|
|
84
|
+
- **Exports:** every public component must be exported from `src/index.ts`
|
|
85
|
+
- **Peers:** React 18, Chakra, Emotion — consumers install alongside library
|
|
86
|
+
- **Build:** `npm run build` compiles to `dist/`; never edit `dist/` directly
|
|
87
|
+
- **Version:** patch releases via `npm run package`; CI publishes on merge to `develop`
|
|
88
|
+
- **Breaking changes:** require version bump + consumer update in frontends
|
|
89
|
+
|
|
90
|
+
## 8. Checklist — adding a new component
|
|
91
|
+
|
|
92
|
+
1. Read similar component + `.cursor/modules/<domain>/MODULE.md`
|
|
93
|
+
2. Create `src/Components/<Name>/` with component + props
|
|
94
|
+
3. Add demo page in `src/Pages/` if needed
|
|
95
|
+
4. Export from `src/index.ts`
|
|
96
|
+
5. Add Jest tests (`*.test.tsx`)
|
|
97
|
+
6. Update `.cursor/modules/<domain>/MODULE.md`
|
|
98
|
+
7. Verify playground: `npm start`
|
|
99
|
+
8. After merge to develop: consumers run `npm run pixelize` in their frontend
|
|
100
|
+
|
|
101
|
+
## 9. Do
|
|
102
|
+
|
|
103
|
+
- Match existing Chakra + design token patterns in `src/Theme/`
|
|
104
|
+
- Keep components framework-agnostic where possible (props in, events out)
|
|
105
|
+
- Add tests for new behavior
|
|
106
|
+
- Use `useCustomTheme` / theme tokens — no hardcoded brand colors
|
|
107
|
+
- Branch from **feature/cursor-ai-setup** or **develop**
|
|
108
|
+
|
|
109
|
+
## 10. Don't
|
|
110
|
+
|
|
111
|
+
- Don't add app-specific business logic (CRM entities, API calls) to library components
|
|
112
|
+
- Don't break existing exports without semver major bump
|
|
113
|
+
- Don't edit `dist/` — always change `src/` and rebuild
|
|
114
|
+
- Don't skip `index.ts` export for public components
|
|
115
|
+
- Don't introduce a second component pattern without team agreement
|
|
116
|
+
- Don't commit npm tokens or `.env` secrets
|
|
117
|
+
|
|
118
|
+
## Task pipeline
|
|
119
|
+
|
|
120
|
+
- **Feature work:** invoke via **`/` menu → `task`** (not by typing `/task` in the message).
|
|
121
|
+
- Cross-repo: **`/ → design-task**`.
|
|
122
|
+
- One-time status line + hook details: [.cursor/TASK-SETUP.md](.cursor/TASK-SETUP.md).
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
import React from "react";
|
|
2
2
|
import { PaymentCardProps } from "./PaymentCardProps";
|
|
3
|
-
declare const PaymentCard: ({ plan, isActive, isNextUpgrade, billingCycle, onSelect, onHover, buttonLoading, isLoading, }: PaymentCardProps) => React.JSX.Element;
|
|
3
|
+
declare const PaymentCard: ({ plan, isActive, isNextUpgrade, billingCycle, onSelect, onHover, buttonLoading, isLoading, isRenewal, }: PaymentCardProps) => React.JSX.Element;
|
|
4
4
|
export default PaymentCard;
|
|
@@ -52,9 +52,9 @@ var getCardStyles = function (isActive, isNextUpgrade, popular, theme) {
|
|
|
52
52
|
};
|
|
53
53
|
};
|
|
54
54
|
var PaymentCard = function (_a) {
|
|
55
|
-
var plan = _a.plan, _b = _a.isActive, isActive = _b === void 0 ? false : _b, _c = _a.isNextUpgrade, isNextUpgrade = _c === void 0 ? false : _c, billingCycle = _a.billingCycle, onSelect = _a.onSelect, onHover = _a.onHover, buttonLoading = _a.buttonLoading, _d = _a.isLoading, isLoading = _d === void 0 ? false : _d;
|
|
55
|
+
var plan = _a.plan, _b = _a.isActive, isActive = _b === void 0 ? false : _b, _c = _a.isNextUpgrade, isNextUpgrade = _c === void 0 ? false : _c, billingCycle = _a.billingCycle, onSelect = _a.onSelect, onHover = _a.onHover, buttonLoading = _a.buttonLoading, _d = _a.isLoading, isLoading = _d === void 0 ? false : _d, _e = _a.isRenewal, isRenewal = _e === void 0 ? false : _e;
|
|
56
56
|
var theme = (0, useCustomTheme_1.useCustomTheme)();
|
|
57
|
-
var
|
|
57
|
+
var _f = getCardStyles(isActive, isNextUpgrade, !!plan.popular, theme), badgeText = _f.badgeText, badgeColor = _f.badgeColor, buttonColor = _f.buttonColor, buttonColor50 = _f.buttonColor50;
|
|
58
58
|
var variantStyles = plan.buttonVariant === "outline"
|
|
59
59
|
? {
|
|
60
60
|
borderColor: buttonColor,
|
|
@@ -93,7 +93,7 @@ var PaymentCard = function (_a) {
|
|
|
93
93
|
react_1.default.createElement(react_2.Text, { fontSize: "0.7rem", fontWeight: "normal", color: theme.colors.gray[600] }, plan.tax)))),
|
|
94
94
|
plan.priceDescription && (react_1.default.createElement(react_2.Text, { color: theme.colors.green[800], mt: 2 }, plan === null || plan === void 0 ? void 0 : plan.priceDescription)),
|
|
95
95
|
react_1.default.createElement(react_2.Text, { color: theme.colors.gray[600], mt: 2 }, plan.description)),
|
|
96
|
-
!isActive && plan.buttonText && (react_1.default.createElement(Button_1.default, __assign({ isLoading: plan.plan_id === buttonLoading, loadingText: plan.buttonLoadingText, width: "full", variant: plan.buttonVariant, size: "lg", onClick: function () { return onSelect === null || onSelect === void 0 ? void 0 : onSelect(plan.plan_id); }, onMouseEnter: function () { return onHover === null || onHover === void 0 ? void 0 : onHover(plan.plan_id, "button", true); }, onMouseLeave: function () { return onHover === null || onHover === void 0 ? void 0 : onHover(plan.plan_id, "button", false); } }, variantStyles), plan.buttonText)),
|
|
96
|
+
((!isActive && plan.buttonText) || isRenewal) && (react_1.default.createElement(Button_1.default, __assign({ isLoading: plan.plan_id === buttonLoading, loadingText: plan.buttonLoadingText, width: "full", variant: plan.buttonVariant, size: "lg", onClick: function () { return onSelect === null || onSelect === void 0 ? void 0 : onSelect(plan.plan_id); }, onMouseEnter: function () { return onHover === null || onHover === void 0 ? void 0 : onHover(plan.plan_id, "button", true); }, onMouseLeave: function () { return onHover === null || onHover === void 0 ? void 0 : onHover(plan.plan_id, "button", false); } }, variantStyles), plan.buttonText)),
|
|
97
97
|
react_1.default.createElement(react_2.VStack, { align: "start", spacing: 3, mt: 6 }, plan.features.map(function (feature, i) { return (react_1.default.createElement(react_2.Flex, { key: i, align: "center" },
|
|
98
98
|
react_1.default.createElement(react_2.Icon, { as: lucide_react_1.Check, color: theme.colors.green[500], mr: 2 }),
|
|
99
99
|
react_1.default.createElement(react_2.Text, { fontSize: "sm" }, feature))); })))))));
|
|
@@ -72,12 +72,35 @@ var Dropdown = (0, react_1.forwardRef)(function (_a, ref) {
|
|
|
72
72
|
handleOptionSelect(optionId, optionLabel);
|
|
73
73
|
};
|
|
74
74
|
// The menu is portaled to <body> (so it sits above everything). Anchor it to
|
|
75
|
-
// the trigger
|
|
75
|
+
// the trigger, then clamp to the viewport so it never overflows off-screen
|
|
76
|
+
// (e.g. when the trigger sits near the right or bottom edge).
|
|
76
77
|
var updateMenuPos = (0, react_1.useCallback)(function () {
|
|
78
|
+
var _a, _b;
|
|
77
79
|
if (!dropdownRef.current)
|
|
78
80
|
return;
|
|
79
81
|
var r = dropdownRef.current.getBoundingClientRect();
|
|
80
|
-
|
|
82
|
+
var gutter = 8;
|
|
83
|
+
var vw = window.innerWidth;
|
|
84
|
+
var vh = window.innerHeight;
|
|
85
|
+
// Use the rendered menu size when available; fall back to trigger width.
|
|
86
|
+
var menuW = ((_a = menuRef.current) === null || _a === void 0 ? void 0 : _a.offsetWidth) || r.width;
|
|
87
|
+
var menuH = ((_b = menuRef.current) === null || _b === void 0 ? void 0 : _b.offsetHeight) || 0;
|
|
88
|
+
// Horizontal: prefer left-aligned to trigger; if it overflows the right
|
|
89
|
+
// edge, right-align to the trigger, then clamp within [gutter, vw-gutter].
|
|
90
|
+
var left = r.left;
|
|
91
|
+
if (left + menuW > vw - gutter)
|
|
92
|
+
left = r.right - menuW;
|
|
93
|
+
left = Math.min(Math.max(left, gutter), Math.max(gutter, vw - menuW - gutter));
|
|
94
|
+
// Vertical: prefer below the trigger; flip above when it would overflow the
|
|
95
|
+
// bottom and there is more room above. Clamp within the viewport.
|
|
96
|
+
var top = r.bottom;
|
|
97
|
+
var spaceBelow = vh - r.bottom;
|
|
98
|
+
var spaceAbove = r.top;
|
|
99
|
+
if (menuH && top + menuH > vh - gutter && spaceAbove > spaceBelow) {
|
|
100
|
+
top = r.top - menuH;
|
|
101
|
+
}
|
|
102
|
+
top = Math.min(Math.max(top, gutter), Math.max(gutter, vh - menuH - gutter));
|
|
103
|
+
setMenuPos({ top: top, left: left, width: r.width });
|
|
81
104
|
}, []);
|
|
82
105
|
(0, react_1.useEffect)(function () {
|
|
83
106
|
if (!isOpen)
|
|
@@ -112,6 +135,9 @@ var Dropdown = (0, react_1.forwardRef)(function (_a, ref) {
|
|
|
112
135
|
ref(node);
|
|
113
136
|
else if (ref)
|
|
114
137
|
ref.current = node;
|
|
138
|
+
// Recompute once the menu has mounted so clamping uses its real size.
|
|
139
|
+
if (node)
|
|
140
|
+
updateMenuPos();
|
|
115
141
|
};
|
|
116
142
|
var Chevron = isVisibleIconShow ? (react_1.default.createElement(react_2.Box, { as: "span", display: "inline-flex", transition: "transform 0.2s ease", transform: isOpen ? "rotate(180deg)" : "rotate(0deg)" },
|
|
117
143
|
react_1.default.createElement(lucide_react_1.ChevronDown, { size: s.chevron }))) : undefined;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import "@testing-library/jest-dom";
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
var react_1 = __importDefault(require("react"));
|
|
7
|
+
var react_2 = require("@testing-library/react");
|
|
8
|
+
require("@testing-library/jest-dom");
|
|
9
|
+
var react_3 = require("@chakra-ui/react");
|
|
10
|
+
var DropDown_1 = __importDefault(require("./DropDown"));
|
|
11
|
+
jest.mock("../../Theme/useCustomTheme", function () { return ({
|
|
12
|
+
useCustomTheme: function () { return ({
|
|
13
|
+
colors: {
|
|
14
|
+
white: "#ffffff",
|
|
15
|
+
gray: { 100: "#f0f0f0", 200: "#e2e8f0", 300: "#cbd5e1", 400: "#94a3b8", 500: "#64748b", 700: "#334155" },
|
|
16
|
+
primary: { 600: "#4f46e5", 700: "#4338ca", opacity: { 8: "rgba(99,102,241,0.08)" } },
|
|
17
|
+
boxborder: { 100: "#eef2f6", 200: "#e2e8f0" },
|
|
18
|
+
text: { 700: "#334155" },
|
|
19
|
+
},
|
|
20
|
+
shadows: { lg: "0 10px 15px rgba(0,0,0,0.1)" },
|
|
21
|
+
}); },
|
|
22
|
+
}); });
|
|
23
|
+
beforeAll(function () {
|
|
24
|
+
Object.defineProperty(window, "matchMedia", {
|
|
25
|
+
writable: true,
|
|
26
|
+
configurable: true,
|
|
27
|
+
value: jest.fn().mockImplementation(function (query) { return ({
|
|
28
|
+
matches: false,
|
|
29
|
+
media: query,
|
|
30
|
+
onchange: null,
|
|
31
|
+
addListener: jest.fn(),
|
|
32
|
+
removeListener: jest.fn(),
|
|
33
|
+
addEventListener: jest.fn(),
|
|
34
|
+
removeEventListener: jest.fn(),
|
|
35
|
+
dispatchEvent: jest.fn(),
|
|
36
|
+
}); }),
|
|
37
|
+
});
|
|
38
|
+
Object.defineProperty(window, "innerWidth", { writable: true, configurable: true, value: 1000 });
|
|
39
|
+
Object.defineProperty(window, "innerHeight", { writable: true, configurable: true, value: 800 });
|
|
40
|
+
});
|
|
41
|
+
var options = [
|
|
42
|
+
{ id: 1, label: "Upload file" },
|
|
43
|
+
{ id: 2, label: "Documents" },
|
|
44
|
+
];
|
|
45
|
+
var renderWithChakra = function (ui) {
|
|
46
|
+
return (0, react_2.render)(react_1.default.createElement(react_3.ChakraProvider, null, ui));
|
|
47
|
+
};
|
|
48
|
+
describe("Dropdown", function () {
|
|
49
|
+
it("renders the trigger and opens the menu on click", function () {
|
|
50
|
+
var onSelect = jest.fn();
|
|
51
|
+
renderWithChakra(react_1.default.createElement(DropDown_1.default, { ButtonText: "Actions", options: options, handleOptionSelect: onSelect }));
|
|
52
|
+
var trigger = react_2.screen.getByRole("button", { name: /actions/i });
|
|
53
|
+
expect(trigger).toBeInTheDocument();
|
|
54
|
+
expect(react_2.screen.queryByText("Upload file")).not.toBeInTheDocument();
|
|
55
|
+
react_2.fireEvent.click(trigger);
|
|
56
|
+
expect(react_2.screen.getByText("Upload file")).toBeInTheDocument();
|
|
57
|
+
expect(react_2.screen.getByText("Documents")).toBeInTheDocument();
|
|
58
|
+
});
|
|
59
|
+
it("calls handleOptionSelect with the chosen option", function () {
|
|
60
|
+
var onSelect = jest.fn();
|
|
61
|
+
renderWithChakra(react_1.default.createElement(DropDown_1.default, { ButtonText: "Actions", options: options, handleOptionSelect: onSelect }));
|
|
62
|
+
react_2.fireEvent.click(react_2.screen.getByRole("button", { name: /actions/i }));
|
|
63
|
+
react_2.fireEvent.click(react_2.screen.getByText("Documents"));
|
|
64
|
+
expect(onSelect).toHaveBeenCalledWith(2, "Documents");
|
|
65
|
+
});
|
|
66
|
+
it("clamps the menu within the viewport when the trigger is near the right edge", function () {
|
|
67
|
+
var onSelect = jest.fn();
|
|
68
|
+
var container = renderWithChakra(react_1.default.createElement(DropDown_1.default, { ButtonText: "Actions", options: options, handleOptionSelect: onSelect })).container;
|
|
69
|
+
var trigger = react_2.screen.getByRole("button", { name: /actions/i });
|
|
70
|
+
var anchor = container.querySelector("div");
|
|
71
|
+
// Trigger sits flush against the right edge (viewport width = 1000).
|
|
72
|
+
jest
|
|
73
|
+
.spyOn(anchor, "getBoundingClientRect")
|
|
74
|
+
.mockReturnValue({
|
|
75
|
+
top: 100,
|
|
76
|
+
bottom: 140,
|
|
77
|
+
left: 960,
|
|
78
|
+
right: 1000,
|
|
79
|
+
width: 40,
|
|
80
|
+
height: 40,
|
|
81
|
+
x: 960,
|
|
82
|
+
y: 100,
|
|
83
|
+
toJSON: function () { return ({}); },
|
|
84
|
+
});
|
|
85
|
+
react_2.fireEvent.click(trigger);
|
|
86
|
+
// Walk up from the rendered option to the fixed-position menu container.
|
|
87
|
+
var node = react_2.screen.getByText("Upload file");
|
|
88
|
+
var menu = null;
|
|
89
|
+
while (node) {
|
|
90
|
+
if (getComputedStyle(node).position === "fixed") {
|
|
91
|
+
menu = node;
|
|
92
|
+
break;
|
|
93
|
+
}
|
|
94
|
+
node = node.parentElement;
|
|
95
|
+
}
|
|
96
|
+
expect(menu).toBeTruthy();
|
|
97
|
+
var left = parseFloat(getComputedStyle(menu).left);
|
|
98
|
+
// Must be pulled left of the trigger so it does not overflow the right edge.
|
|
99
|
+
expect(left).toBeLessThan(960);
|
|
100
|
+
expect(left).toBeGreaterThanOrEqual(8);
|
|
101
|
+
});
|
|
102
|
+
});
|
|
@@ -78,10 +78,11 @@ describe("OtherApps", function () {
|
|
|
78
78
|
beforeEach(function () {
|
|
79
79
|
jest.clearAllMocks();
|
|
80
80
|
});
|
|
81
|
-
it("
|
|
81
|
+
it("renders the section header and inline labels when expanded", function () {
|
|
82
82
|
renderWithChakra(react_1.default.createElement(OtherApps_1.default, { toggle: false, otherApps: sampleApps }));
|
|
83
|
-
expect(react_2.screen.getByText("
|
|
83
|
+
expect(react_2.screen.getByText("Switch app")).toBeInTheDocument();
|
|
84
84
|
expect(react_2.screen.getByText("Billing")).toBeInTheDocument();
|
|
85
|
+
expect(react_2.screen.getByText("Tickets")).toBeInTheDocument();
|
|
85
86
|
expect(react_2.screen.queryAllByRole("tooltip")).toHaveLength(0);
|
|
86
87
|
});
|
|
87
88
|
it("shows flyout with Open label on hover when collapsed", function () { return __awaiter(void 0, void 0, void 0, function () {
|