From 86c46abe7e193af33eb647b13a8066e87465affa Mon Sep 17 00:00:00 2001
From: ahmadtaimoor-deriv
<129935294+ahmadtaimoor-deriv@users.noreply.github.com>
Date: Tue, 4 Feb 2025 11:14:17 +0800
Subject: [PATCH 1/7] BottomSheet component
---
package-lock.json | 315 +++++++++++++++++-
package.json | 2 +
.../BottomSheet/BottomSheet.example.tsx | 30 ++
src/components/BottomSheet/BottomSheet.tsx | 101 ++++++
.../__tests__/BottomSheet.test.tsx | 89 +++++
src/components/BottomSheet/index.ts | 1 +
src/config/bottomSheetConfig.tsx | 19 ++
src/screens/TradePage/TradePage.tsx | 54 +--
src/stores/bottomSheetStore.ts | 19 ++
9 files changed, 606 insertions(+), 24 deletions(-)
create mode 100644 src/components/BottomSheet/BottomSheet.example.tsx
create mode 100644 src/components/BottomSheet/BottomSheet.tsx
create mode 100644 src/components/BottomSheet/__tests__/BottomSheet.test.tsx
create mode 100644 src/components/BottomSheet/index.ts
create mode 100644 src/config/bottomSheetConfig.tsx
create mode 100644 src/stores/bottomSheetStore.ts
diff --git a/package-lock.json b/package-lock.json
index b099182..74e9140 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -8,6 +8,8 @@
"name": "champion-trader",
"version": "0.0.0",
"dependencies": {
+ "@radix-ui/react-dialog": "^1.1.5",
+ "@radix-ui/react-presence": "^1.1.2",
"@radix-ui/react-slot": "^1.1.1",
"@radix-ui/react-switch": "^1.1.2",
"@radix-ui/react-toggle": "^1.0.3",
@@ -1355,6 +1357,168 @@
}
}
},
+ "node_modules/@radix-ui/react-dialog": {
+ "version": "1.1.5",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-dialog/-/react-dialog-1.1.5.tgz",
+ "integrity": "sha512-LaO3e5h/NOEL4OfXjxD43k9Dx+vn+8n+PCFt6uhX/BADFflllyv3WJG6rgvvSVBxpTch938Qq/LGc2MMxipXPw==",
+ "dependencies": {
+ "@radix-ui/primitive": "1.1.1",
+ "@radix-ui/react-compose-refs": "1.1.1",
+ "@radix-ui/react-context": "1.1.1",
+ "@radix-ui/react-dismissable-layer": "1.1.4",
+ "@radix-ui/react-focus-guards": "1.1.1",
+ "@radix-ui/react-focus-scope": "1.1.1",
+ "@radix-ui/react-id": "1.1.0",
+ "@radix-ui/react-portal": "1.1.3",
+ "@radix-ui/react-presence": "1.1.2",
+ "@radix-ui/react-primitive": "2.0.1",
+ "@radix-ui/react-slot": "1.1.1",
+ "@radix-ui/react-use-controllable-state": "1.1.0",
+ "aria-hidden": "^1.2.4",
+ "react-remove-scroll": "^2.6.2"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-dismissable-layer": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.4.tgz",
+ "integrity": "sha512-XDUI0IVYVSwjMXxM6P4Dfti7AH+Y4oS/TB+sglZ/EXc7cqLwGAmp1NlMrcUjj7ks6R5WTZuWKv44FBbLpwU3sA==",
+ "dependencies": {
+ "@radix-ui/primitive": "1.1.1",
+ "@radix-ui/react-compose-refs": "1.1.1",
+ "@radix-ui/react-primitive": "2.0.1",
+ "@radix-ui/react-use-callback-ref": "1.1.0",
+ "@radix-ui/react-use-escape-keydown": "1.1.0"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-focus-guards": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-guards/-/react-focus-guards-1.1.1.tgz",
+ "integrity": "sha512-pSIwfrT1a6sIoDASCSpFwOasEwKTZWDw/iBdtnqKO7v6FeOzYJ7U53cPzYFVR3geGGXgVHaH+CdngrrAzqUGxg==",
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-focus-scope": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-scope/-/react-focus-scope-1.1.1.tgz",
+ "integrity": "sha512-01omzJAYRxXdG2/he/+xy+c8a8gCydoQ1yOxnWNcRhrrBW5W+RQJ22EK1SaO8tb3WoUsuEw7mJjBozPzihDFjA==",
+ "dependencies": {
+ "@radix-ui/react-compose-refs": "1.1.1",
+ "@radix-ui/react-primitive": "2.0.1",
+ "@radix-ui/react-use-callback-ref": "1.1.0"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-id": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.1.0.tgz",
+ "integrity": "sha512-EJUrI8yYh7WOjNOqpoJaf1jlFIH2LvtgAl+YcFqNCa+4hj64ZXmPkAKOFs/ukjz3byN6bdb/AVUqHkI8/uWWMA==",
+ "dependencies": {
+ "@radix-ui/react-use-layout-effect": "1.1.0"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-portal": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.1.3.tgz",
+ "integrity": "sha512-NciRqhXnGojhT93RPyDaMPfLH3ZSl4jjIFbZQ1b/vxvZEdHsBZ49wP9w8L3HzUQwep01LcWtkUvm0OVB5JAHTw==",
+ "dependencies": {
+ "@radix-ui/react-primitive": "2.0.1",
+ "@radix-ui/react-use-layout-effect": "1.1.0"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-presence": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.2.tgz",
+ "integrity": "sha512-18TFr80t5EVgL9x1SwF/YGtfG+l0BS0PRAlCWBDoBEiDQjeKgnNZRVJp/oVBl24sr3Gbfwc/Qpj4OcWTQMsAEg==",
+ "dependencies": {
+ "@radix-ui/react-compose-refs": "1.1.1",
+ "@radix-ui/react-use-layout-effect": "1.1.0"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
"node_modules/@radix-ui/react-primitive": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.0.1.tgz",
@@ -1477,6 +1641,23 @@
}
}
},
+ "node_modules/@radix-ui/react-use-escape-keydown": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-use-escape-keydown/-/react-use-escape-keydown-1.1.0.tgz",
+ "integrity": "sha512-L7vwWlR1kTTQ3oh7g1O0CBF3YCyyTj8NmhLR+phShpyA50HCfBFKVJTpshm9PzLiKmehsrQzTYTpX9HvmC9rhw==",
+ "dependencies": {
+ "@radix-ui/react-use-callback-ref": "1.1.0"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
"node_modules/@radix-ui/react-use-layout-effect": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.0.tgz",
@@ -2538,6 +2719,17 @@
"sprintf-js": "~1.0.2"
}
},
+ "node_modules/aria-hidden": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/aria-hidden/-/aria-hidden-1.2.4.tgz",
+ "integrity": "sha512-y+CcFFwelSXpLZk/7fMB2mUbGtX9lKycf1MWJ7CaTIERyitVlyQx6C+sxcROU2BAJ24OiZyK+8wj2i8AlBoS3A==",
+ "dependencies": {
+ "tslib": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
"node_modules/aria-query": {
"version": "5.3.0",
"resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz",
@@ -3471,6 +3663,11 @@
"node": ">=8"
}
},
+ "node_modules/detect-node-es": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/detect-node-es/-/detect-node-es-1.1.0.tgz",
+ "integrity": "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ=="
+ },
"node_modules/didyoumean": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz",
@@ -4398,6 +4595,14 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/get-nonce": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/get-nonce/-/get-nonce-1.0.1.tgz",
+ "integrity": "sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==",
+ "engines": {
+ "node": ">=6"
+ }
+ },
"node_modules/get-package-type": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz",
@@ -7319,6 +7524,51 @@
"node": ">=0.10.0"
}
},
+ "node_modules/react-remove-scroll": {
+ "version": "2.6.3",
+ "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.6.3.tgz",
+ "integrity": "sha512-pnAi91oOk8g8ABQKGF5/M9qxmmOPxaAnopyTHYfqYEwJhyFrbbBtHuSgtKEoH0jpcxx5o3hXqH1mNd9/Oi+8iQ==",
+ "dependencies": {
+ "react-remove-scroll-bar": "^2.3.7",
+ "react-style-singleton": "^2.2.3",
+ "tslib": "^2.1.0",
+ "use-callback-ref": "^1.3.3",
+ "use-sidecar": "^1.1.3"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/react-remove-scroll-bar": {
+ "version": "2.3.8",
+ "resolved": "https://registry.npmjs.org/react-remove-scroll-bar/-/react-remove-scroll-bar-2.3.8.tgz",
+ "integrity": "sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q==",
+ "dependencies": {
+ "react-style-singleton": "^2.2.2",
+ "tslib": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
"node_modules/react-router": {
"version": "7.1.3",
"resolved": "https://registry.npmjs.org/react-router/-/react-router-7.1.3.tgz",
@@ -7357,6 +7607,27 @@
"react-dom": ">=18"
}
},
+ "node_modules/react-style-singleton": {
+ "version": "2.2.3",
+ "resolved": "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.3.tgz",
+ "integrity": "sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ==",
+ "dependencies": {
+ "get-nonce": "^1.0.0",
+ "tslib": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
"node_modules/read-cache": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz",
@@ -8375,8 +8646,7 @@
"node_modules/tslib": {
"version": "2.8.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
- "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
- "devOptional": true
+ "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="
},
"node_modules/turbo-stream": {
"version": "2.4.0",
@@ -8492,6 +8762,47 @@
"requires-port": "^1.0.0"
}
},
+ "node_modules/use-callback-ref": {
+ "version": "1.3.3",
+ "resolved": "https://registry.npmjs.org/use-callback-ref/-/use-callback-ref-1.3.3.tgz",
+ "integrity": "sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg==",
+ "dependencies": {
+ "tslib": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/use-sidecar": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/use-sidecar/-/use-sidecar-1.1.3.tgz",
+ "integrity": "sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ==",
+ "dependencies": {
+ "detect-node-es": "^1.1.0",
+ "tslib": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
"node_modules/use-sync-external-store": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.4.0.tgz",
diff --git a/package.json b/package.json
index fe8ac32..84ed1bb 100644
--- a/package.json
+++ b/package.json
@@ -12,6 +12,8 @@
"test": "jest --config jest.config.cjs"
},
"dependencies": {
+ "@radix-ui/react-dialog": "^1.1.5",
+ "@radix-ui/react-presence": "^1.1.2",
"@radix-ui/react-slot": "^1.1.1",
"@radix-ui/react-switch": "^1.1.2",
"@radix-ui/react-toggle": "^1.0.3",
diff --git a/src/components/BottomSheet/BottomSheet.example.tsx b/src/components/BottomSheet/BottomSheet.example.tsx
new file mode 100644
index 0000000..bfbac3c
--- /dev/null
+++ b/src/components/BottomSheet/BottomSheet.example.tsx
@@ -0,0 +1,30 @@
+import React from 'react';
+import { BottomSheet } from './BottomSheet';
+import { useBottomSheetStore } from '@/stores/bottomSheetStore';
+
+export const BottomSheetExample = () => {
+ const { setBottomSheet } = useBottomSheetStore();
+
+ const examples = [
+ { height: '50%', label: '50% Height' },
+ { height: '380px', label: '380px Height' },
+ { height: '75vh', label: '75vh Height' },
+ ];
+
+ return (
+
+ {examples.map(({ height, label }) => (
+
+ ))}
+
+ {/* The BottomSheet component should be rendered at the app root level */}
+
+
+ );
+};
diff --git a/src/components/BottomSheet/BottomSheet.tsx b/src/components/BottomSheet/BottomSheet.tsx
new file mode 100644
index 0000000..d474266
--- /dev/null
+++ b/src/components/BottomSheet/BottomSheet.tsx
@@ -0,0 +1,101 @@
+import { useRef, useCallback } from "react";
+import { useBottomSheetStore } from "@/stores/bottomSheetStore";
+import { bottomSheetConfig } from "@/config/bottomSheetConfig";
+
+export const BottomSheet = () => {
+ const { showBottomSheet, key, height, setBottomSheet } = useBottomSheetStore();
+
+ const sheetRef = useRef(null);
+ const dragStartY = useRef(0);
+ const currentY = useRef(0);
+ const isDragging = useRef(false);
+
+ const handleTouchStart = useCallback((e: React.TouchEvent) => {
+ const touch = e.touches[0];
+ dragStartY.current = touch.clientY;
+ currentY.current = 0;
+ isDragging.current = true;
+
+ document.addEventListener("touchmove", handleTouchMove);
+ document.addEventListener("touchend", handleTouchEnd);
+ }, []);
+
+ const handleTouchMove = useCallback((e: TouchEvent) => {
+ if (!sheetRef.current || !isDragging.current) return;
+
+ const touch = e.touches[0];
+ const deltaY = touch.clientY - dragStartY.current;
+ currentY.current = deltaY;
+
+ if (deltaY > 0) {
+ sheetRef.current.style.transform = `translateY(${deltaY}px)`;
+ }
+ }, []);
+
+ const handleTouchEnd = useCallback(() => {
+ if (!sheetRef.current) return;
+
+ document.removeEventListener("touchmove", handleTouchMove);
+ document.removeEventListener("touchend", handleTouchEnd);
+ isDragging.current = false;
+
+ sheetRef.current.style.transform = "";
+
+ if (currentY.current > 100) {
+ setBottomSheet(false);
+ }
+ }, [setBottomSheet]);
+
+ const body = key ? bottomSheetConfig[key]?.body : null;
+
+ if (!showBottomSheet || !body) return null;
+
+ // Convert percentage to vh for height if needed
+ const processedHeight = height.endsWith('%')
+ ? `${parseFloat(height)}vh`
+ : height;
+
+ return (
+ <>
+ {/* Overlay */}
+
+
+ {/* Sheet */}
+
+ {/* Handle Bar */}
+
+
+ {/* Content */}
+
+ {body}
+
+
+ >
+ );
+};
diff --git a/src/components/BottomSheet/__tests__/BottomSheet.test.tsx b/src/components/BottomSheet/__tests__/BottomSheet.test.tsx
new file mode 100644
index 0000000..583f9f4
--- /dev/null
+++ b/src/components/BottomSheet/__tests__/BottomSheet.test.tsx
@@ -0,0 +1,89 @@
+import { render, screen, fireEvent } from "@testing-library/react";
+import { BottomSheet } from "../BottomSheet";
+
+describe("BottomSheet", () => {
+ const mockHeader = Test Header
;
+ const mockBody = Test Body
;
+ const mockFooter = Test Footer
;
+ const mockOnClose = jest.fn();
+
+ it("renders header, body, and footer correctly", () => {
+ render(
+
+ );
+
+ expect(screen.getByText("Test Header")).toBeInTheDocument();
+ expect(screen.getByText("Test Body")).toBeInTheDocument();
+ expect(screen.getByText("Test Footer")).toBeInTheDocument();
+ });
+
+ it("applies custom height when provided", () => {
+ const { container } = render(
+
+ );
+
+ const bottomSheet = container.firstChild as HTMLElement;
+ expect(bottomSheet).toHaveStyle({ height: "50%" });
+ });
+
+ it("applies full screen height when isFullScreen is true", () => {
+ const { container } = render(
+
+ );
+
+ const bottomSheet = container.firstChild as HTMLElement;
+ expect(bottomSheet).toHaveStyle({ height: "100vh" });
+ });
+
+ it("calls onClose when backdrop is clicked", () => {
+ render(
+
+ );
+
+ const backdrop = screen.getByTestId("bottom-sheet-backdrop");
+ fireEvent.click(backdrop);
+ expect(mockOnClose).toHaveBeenCalled();
+ });
+
+ it("does not render when isOpen is false", () => {
+ render(
+
+ );
+
+ expect(screen.queryByText("Test Header")).not.toBeInTheDocument();
+ expect(screen.queryByText("Test Body")).not.toBeInTheDocument();
+ expect(screen.queryByText("Test Footer")).not.toBeInTheDocument();
+ });
+});
diff --git a/src/components/BottomSheet/index.ts b/src/components/BottomSheet/index.ts
new file mode 100644
index 0000000..3f8a2c8
--- /dev/null
+++ b/src/components/BottomSheet/index.ts
@@ -0,0 +1 @@
+export { BottomSheet } from "./BottomSheet";
diff --git a/src/config/bottomSheetConfig.tsx b/src/config/bottomSheetConfig.tsx
new file mode 100644
index 0000000..f0eb5e1
--- /dev/null
+++ b/src/config/bottomSheetConfig.tsx
@@ -0,0 +1,19 @@
+import { ReactNode } from 'react';
+
+export interface BottomSheetConfig {
+ [key: string]: {
+ body: ReactNode;
+ };
+}
+
+export const bottomSheetConfig: BottomSheetConfig = {
+ 'rise-contract': {
+ body: (
+
+ )
+ }
+};
diff --git a/src/screens/TradePage/TradePage.tsx b/src/screens/TradePage/TradePage.tsx
index b6b1848..f700b73 100644
--- a/src/screens/TradePage/TradePage.tsx
+++ b/src/screens/TradePage/TradePage.tsx
@@ -1,16 +1,18 @@
-import React, { Suspense } from "react";
-import { TradeButton } from "@/components/TradeButton";
-import { Chart } from "@/components/Chart";
-import { AddMarketButton } from "@/components/AddMarketButton";
-import { DurationOptions } from "@/components/DurationOptions";
-import { useTradeStore } from "@/stores/tradeStore";
-import { Card, CardContent } from "@/components/ui/card";
-import TradeParam from "@/components/TradeFields/TradeParam";
-import ToggleButton from "@/components/TradeFields/ToggleButton";
+import React, { Suspense } from "react"
+import { TradeButton } from "@/components/TradeButton"
+import { Chart } from "@/components/Chart"
+import { BottomSheet } from "@/components/BottomSheet"
+import { AddMarketButton } from "@/components/AddMarketButton"
+import { DurationOptions } from "@/components/DurationOptions"
+import { useTradeStore } from "@/stores/tradeStore"
+import { useBottomSheetStore } from "@/stores/bottomSheetStore"
+import { Card, CardContent } from "@/components/ui/card"
+import TradeParam from "@/components/TradeFields/TradeParam"
+import ToggleButton from "@/components/TradeFields/ToggleButton"
interface MarketInfoProps {
- title: string;
- subtitle: string;
+ title: string
+ subtitle: string
}
const MarketInfo: React.FC = ({ title, subtitle }) => (
@@ -22,10 +24,11 @@ const MarketInfo: React.FC = ({ title, subtitle }) => (
-);
+)
export const TradePage: React.FC = () => {
- const { stake, duration, allowEquals, toggleAllowEquals } = useTradeStore();
+ const { stake, duration, allowEquals, toggleAllowEquals } = useTradeStore()
+ const { setBottomSheet } = useBottomSheetStore()
return (
@@ -67,13 +70,18 @@ export const TradePage: React.FC = () => {
Loading...
}>
-
+
setBottomSheet(true, 'rise-contract')}
+ >
+
+
Loading... }>
{
+
+
- );
-};
+ )
+}
diff --git a/src/stores/bottomSheetStore.ts b/src/stores/bottomSheetStore.ts
new file mode 100644
index 0000000..5f89a56
--- /dev/null
+++ b/src/stores/bottomSheetStore.ts
@@ -0,0 +1,19 @@
+import { create } from 'zustand';
+
+interface BottomSheetState {
+ showBottomSheet: boolean;
+ key: string | null;
+ height: string;
+ setBottomSheet: (show: boolean, key?: string, height?: string) => void;
+}
+
+export const useBottomSheetStore = create((set) => ({
+ showBottomSheet: false,
+ key: null,
+ height: '400px',
+ setBottomSheet: (show: boolean, key?: string, height?: string) => set({
+ showBottomSheet: show,
+ key: show ? key || null : null,
+ height: height || '400px'
+ }),
+}));
From 34292e184c4cb7453948b73ea907387e259eb243 Mon Sep 17 00:00:00 2001
From: ahmadtaimoor-deriv
<129935294+ahmadtaimoor-deriv@users.noreply.github.com>
Date: Tue, 4 Feb 2025 12:48:25 +0800
Subject: [PATCH 2/7] setting it to stake
---
.../__tests__/BottomSheet.test.tsx | 166 ++++++++++--------
src/components/TradeFields/TradeParam.tsx | 22 ++-
src/config/bottomSheetConfig.tsx | 4 +-
src/screens/TradePage/TradePage.tsx | 15 +-
4 files changed, 117 insertions(+), 90 deletions(-)
diff --git a/src/components/BottomSheet/__tests__/BottomSheet.test.tsx b/src/components/BottomSheet/__tests__/BottomSheet.test.tsx
index 583f9f4..c8ba321 100644
--- a/src/components/BottomSheet/__tests__/BottomSheet.test.tsx
+++ b/src/components/BottomSheet/__tests__/BottomSheet.test.tsx
@@ -1,89 +1,105 @@
import { render, screen, fireEvent } from "@testing-library/react";
import { BottomSheet } from "../BottomSheet";
+import { useBottomSheetStore } from "@/stores/bottomSheetStore";
+
+// Mock the store and its types
+const mockUseBottomSheetStore = useBottomSheetStore as unknown as jest.Mock;
+jest.mock("@/stores/bottomSheetStore", () => ({
+ useBottomSheetStore: jest.fn()
+}));
+
+// Mock the config
+jest.mock("@/config/bottomSheetConfig", () => ({
+ bottomSheetConfig: {
+ 'test-key': {
+ body: Test Body Content
+ }
+ }
+}));
describe("BottomSheet", () => {
- const mockHeader = Test Header
;
- const mockBody = Test Body
;
- const mockFooter = Test Footer
;
- const mockOnClose = jest.fn();
-
- it("renders header, body, and footer correctly", () => {
- render(
-
- );
-
- expect(screen.getByText("Test Header")).toBeInTheDocument();
- expect(screen.getByText("Test Body")).toBeInTheDocument();
- expect(screen.getByText("Test Footer")).toBeInTheDocument();
+ const mockSetBottomSheet = jest.fn();
+
+ beforeEach(() => {
+ // Reset mocks
+ jest.clearAllMocks();
});
- it("applies custom height when provided", () => {
- const { container } = render(
-
- );
-
- const bottomSheet = container.firstChild as HTMLElement;
- expect(bottomSheet).toHaveStyle({ height: "50%" });
+ it("renders body content when showBottomSheet is true", () => {
+ mockUseBottomSheetStore.mockReturnValue({
+ showBottomSheet: true,
+ key: 'test-key',
+ height: '380px',
+ setBottomSheet: mockSetBottomSheet
+ });
+
+ render();
+
+ expect(screen.getByText("Test Body Content")).toBeInTheDocument();
+ });
+
+ it("does not render when showBottomSheet is false", () => {
+ mockUseBottomSheetStore.mockReturnValue({
+ showBottomSheet: false,
+ key: null,
+ height: '380px',
+ setBottomSheet: mockSetBottomSheet
+ });
+
+ render();
+
+ expect(screen.queryByText("Test Body Content")).not.toBeInTheDocument();
});
- it("applies full screen height when isFullScreen is true", () => {
- const { container } = render(
-
- );
-
- const bottomSheet = container.firstChild as HTMLElement;
- expect(bottomSheet).toHaveStyle({ height: "100vh" });
+ it("applies custom height from store", () => {
+ mockUseBottomSheetStore.mockReturnValue({
+ showBottomSheet: true,
+ key: 'test-key',
+ height: '50%',
+ setBottomSheet: mockSetBottomSheet
+ });
+
+ const { container } = render();
+
+ const bottomSheet = container.querySelector('[class*="fixed bottom-0"]');
+ expect(bottomSheet).toHaveStyle({ height: '50vh' });
});
- it("calls onClose when backdrop is clicked", () => {
- render(
-
- );
-
- const backdrop = screen.getByTestId("bottom-sheet-backdrop");
- fireEvent.click(backdrop);
- expect(mockOnClose).toHaveBeenCalled();
+ it("handles drag to dismiss", () => {
+ mockUseBottomSheetStore.mockReturnValue({
+ showBottomSheet: true,
+ key: 'test-key',
+ height: '380px',
+ setBottomSheet: mockSetBottomSheet
+ });
+
+ const { container } = render();
+
+ const handleBar = container.querySelector('[class*="flex flex-col items-center"]');
+ expect(handleBar).toBeInTheDocument();
+
+ // Simulate drag down
+ fireEvent.touchStart(handleBar!, { touches: [{ clientY: 0 }] });
+ fireEvent.touchMove(document, { touches: [{ clientY: 150 }] });
+ fireEvent.touchEnd(document);
+
+ expect(mockSetBottomSheet).toHaveBeenCalledWith(false);
});
- it("does not render when isOpen is false", () => {
- render(
-
- );
-
- expect(screen.queryByText("Test Header")).not.toBeInTheDocument();
- expect(screen.queryByText("Test Body")).not.toBeInTheDocument();
- expect(screen.queryByText("Test Footer")).not.toBeInTheDocument();
+ it("does not close when clicking overlay", () => {
+ mockUseBottomSheetStore.mockReturnValue({
+ showBottomSheet: true,
+ key: 'test-key',
+ height: '380px',
+ setBottomSheet: mockSetBottomSheet
+ });
+
+ const { container } = render();
+
+ const overlay = container.querySelector('[class*="fixed inset-0"]');
+ expect(overlay).toBeInTheDocument();
+ fireEvent.click(overlay!);
+
+ expect(mockSetBottomSheet).not.toHaveBeenCalled();
});
});
diff --git a/src/components/TradeFields/TradeParam.tsx b/src/components/TradeFields/TradeParam.tsx
index 1831326..26b6804 100644
--- a/src/components/TradeFields/TradeParam.tsx
+++ b/src/components/TradeFields/TradeParam.tsx
@@ -3,15 +3,21 @@ import { Card, CardContent } from "@/components/ui/card";
interface TradeParamProps {
label: string;
value: string;
+ onClick?: () => void;
}
-const TradeParam: React.FC = ({ label, value }) => (
-
-
- {label}
- {value}
-
-
-);
+const TradeParam: React.FC = ({ label, value, onClick }) => {
+ return (
+
+
+ {label}
+ {value}
+
+
+ );
+};
export default TradeParam;
diff --git a/src/config/bottomSheetConfig.tsx b/src/config/bottomSheetConfig.tsx
index f0eb5e1..d5dfe9b 100644
--- a/src/config/bottomSheetConfig.tsx
+++ b/src/config/bottomSheetConfig.tsx
@@ -7,11 +7,11 @@ export interface BottomSheetConfig {
}
export const bottomSheetConfig: BottomSheetConfig = {
- 'rise-contract': {
+ 'stake': {
body: (
)
diff --git a/src/screens/TradePage/TradePage.tsx b/src/screens/TradePage/TradePage.tsx
index f700b73..54259c3 100644
--- a/src/screens/TradePage/TradePage.tsx
+++ b/src/screens/TradePage/TradePage.tsx
@@ -30,6 +30,10 @@ export const TradePage: React.FC = () => {
const { stake, duration, allowEquals, toggleAllowEquals } = useTradeStore()
const { setBottomSheet } = useBottomSheetStore()
+ const handleStakeClick = () => {
+ setBottomSheet(true, 'stake');
+ };
+
return (
{
-
+
@@ -70,10 +78,7 @@ export const TradePage: React.FC = () => {
Loading...
}>
-
setBottomSheet(true, 'rise-contract')}
- >
+
Date: Tue, 4 Feb 2025 12:50:30 +0800
Subject: [PATCH 3/7] trigger
From 6e9d869e9e44c55539601b2882721afb4483d75e Mon Sep 17 00:00:00 2001
From: ahmadtaimoor-deriv
<129935294+ahmadtaimoor-deriv@users.noreply.github.com>
Date: Tue, 4 Feb 2025 13:24:13 +0800
Subject: [PATCH 4/7] review changes
---
src/components/BottomSheet/BottomSheet.tsx | 26 ++-
src/components/README.md | 216 ++++++++++++---------
2 files changed, 137 insertions(+), 105 deletions(-)
diff --git a/src/components/BottomSheet/BottomSheet.tsx b/src/components/BottomSheet/BottomSheet.tsx
index d474266..29fa8c9 100644
--- a/src/components/BottomSheet/BottomSheet.tsx
+++ b/src/components/BottomSheet/BottomSheet.tsx
@@ -1,4 +1,4 @@
-import { useRef, useCallback } from "react";
+import { useRef, useCallback, useEffect } from "react";
import { useBottomSheetStore } from "@/stores/bottomSheetStore";
import { bottomSheetConfig } from "@/config/bottomSheetConfig";
@@ -15,9 +15,6 @@ export const BottomSheet = () => {
dragStartY.current = touch.clientY;
currentY.current = 0;
isDragging.current = true;
-
- document.addEventListener("touchmove", handleTouchMove);
- document.addEventListener("touchend", handleTouchEnd);
}, []);
const handleTouchMove = useCallback((e: TouchEvent) => {
@@ -35,10 +32,7 @@ export const BottomSheet = () => {
const handleTouchEnd = useCallback(() => {
if (!sheetRef.current) return;
- document.removeEventListener("touchmove", handleTouchMove);
- document.removeEventListener("touchend", handleTouchEnd);
isDragging.current = false;
-
sheetRef.current.style.transform = "";
if (currentY.current > 100) {
@@ -46,6 +40,18 @@ export const BottomSheet = () => {
}
}, [setBottomSheet]);
+ useEffect(() => {
+ if (showBottomSheet) {
+ document.addEventListener("touchmove", handleTouchMove);
+ document.addEventListener("touchend", handleTouchEnd);
+
+ return () => {
+ document.removeEventListener("touchmove", handleTouchMove);
+ document.removeEventListener("touchend", handleTouchEnd);
+ };
+ }
+ }, [showBottomSheet, handleTouchMove, handleTouchEnd]);
+
const body = key ? bottomSheetConfig[key]?.body : null;
if (!showBottomSheet || !body) return null;
@@ -59,7 +65,7 @@ export const BottomSheet = () => {
<>
{/* Overlay */}
{/* Sheet */}
@@ -72,7 +78,7 @@ export const BottomSheet = () => {
max-w-[800px]
w-full
mx-auto
- bg-white
+ bg-background
rounded-t-[16px]
animate-in fade-in-0 slide-in-from-bottom
duration-300
@@ -87,7 +93,7 @@ export const BottomSheet = () => {
onTouchStart={handleTouchStart}
>
diff --git a/src/components/README.md b/src/components/README.md
index 12ae5f5..e6b8da3 100644
--- a/src/components/README.md
+++ b/src/components/README.md
@@ -1,101 +1,127 @@
-# Component Architecture
+# Components Documentation
+
+## Table of Contents
+- [BottomSheet](#bottomsheet)
+- [TradeParam](#tradeparam)
+- [TradeButton](#tradebutton)
+- [Chart](#chart)
+- [DurationOptions](#durationoptions)
+- [AddMarketButton](#addmarketbutton)
+
+## BottomSheet
+
+A reusable bottom sheet component with drag-to-dismiss functionality.
+
+### Key Features
+- Single instance pattern using Zustand store
+- Dynamic height support (%, px, vh)
+- Theme-aware using Tailwind CSS variables
+- Drag gesture support with proper event cleanup
+- Content management through configuration
+
+### Usage
+
+```tsx
+// 1. Configure content in bottomSheetConfig.tsx
+export const bottomSheetConfig = {
+ 'my-key': {
+ body:
+ }
+};
+
+// 2. Place component at root level
+
+
+// 3. Control from anywhere using the store
+const { setBottomSheet } = useBottomSheetStore();
+setBottomSheet(true, 'my-key', '50%');
+```
+
+### State Management
+```typescript
+interface BottomSheetState {
+ showBottomSheet: boolean;
+ key: string | null;
+ height: string;
+ setBottomSheet: (show: boolean, key?: string, height?: string) => void;
+}
+```
-This directory contains React components following Atomic Component Design principles. The components are organized to be modular, self-contained, and independently testable.
+### Recent Changes
+- Removed click-outside-to-close behavior
+- Added proper event listener cleanup
+- Migrated to theme-aware colors using Tailwind CSS variables
+- Improved touch event handling
+- Added dynamic height support
-## Component Organization
+## TradeParam
+A card component for displaying trade parameters.
+
+### Usage
+```tsx
+
```
-components/
-├── AddMarketButton/ # Market selection functionality
-├── BottomNav/ # Bottom navigation bar
-├── Chart/ # Trading chart visualization
-├── DurationOptions/ # Trade duration selection
-├── TradeButton/ # Trade execution controls
-├── TradeFields/ # Trade parameter inputs
-└── ui/ # Shared UI components
+
+### Props
+```typescript
+interface TradeParamProps {
+ label: string;
+ value: string;
+ onClick?: () => void;
+}
```
-## Design Principles
-
-1. **Atomic Design**
- - Components are built from smallest to largest
- - Each component has a single responsibility
- - Components are self-contained with their own styles and logic
-
-2. **Styling**
- - Uses TailwindCSS for consistent styling
- - Styles are encapsulated within components
- - Follows utility-first CSS principles
-
-3. **State Management**
- - Local state for component-specific logic
- - Zustand for shared/global state
- - Props for component configuration
-
-4. **Testing**
- - Each component has its own test suite
- - Tests cover component rendering and interactions
- - Mock external dependencies when needed
-
-## Component Guidelines
-
-1. **File Structure**
- ```
- ComponentName/
- ├── ComponentName.tsx # Main component implementation
- ├── index.ts # Public exports
- └── __tests__/ # Test files
- └── ComponentName.test.tsx
- ```
-
-2. **Component Implementation**
- ```typescript
- import { useState } from 'react';
- import { cn } from '@/lib/utils';
-
- interface ComponentProps {
- // Props interface
- }
-
- export function Component({ ...props }: ComponentProps) {
- // Implementation
- }
- ```
-
-3. **Testing Pattern**
- ```typescript
- import { render, screen } from '@testing-library/react';
- import { Component } from './Component';
-
- describe('Component', () => {
- it('should render correctly', () => {
- render(
);
- // Assertions
- });
- });
- ```
-
-## Best Practices
-
-1. **Component Design**
- - Keep components focused and single-purpose
- - Use TypeScript interfaces for props
- - Implement proper error boundaries
- - Handle loading and error states
-
-2. **Performance**
- - Implement lazy loading where appropriate
- - Memoize expensive calculations
- - Optimize re-renders using React.memo when needed
-
-3. **Accessibility**
- - Use semantic HTML elements
- - Include ARIA attributes where necessary
- - Ensure keyboard navigation support
- - Maintain proper color contrast
-
-4. **Code Quality**
- - Follow consistent naming conventions
- - Document complex logic
- - Write comprehensive tests
- - Use proper TypeScript types
+### Recent Changes
+- Made component purely presentational
+- Added optional onClick handler
+- Cursor pointer only shows when onClick is provided
+
+## TradeButton
+
+[Documentation for TradeButton component]
+
+## Chart
+
+[Documentation for Chart component]
+
+## DurationOptions
+
+[Documentation for DurationOptions component]
+
+## AddMarketButton
+
+[Documentation for AddMarketButton component]
+
+---
+
+## Component Design Principles
+
+### 1. State Management
+- Use Zustand for global state
+- Keep components as pure as possible
+- Pass event handlers from parent components
+
+### 2. Styling
+- Use Tailwind CSS with theme variables
+- Follow design system color tokens
+- Ensure dark mode compatibility
+
+### 3. Performance
+- Implement proper cleanup
+- Use React.memo where beneficial
+- Lazy load when appropriate
+
+### 4. Accessibility
+- Follow WCAG guidelines
+- Ensure proper keyboard navigation
+- Maintain appropriate color contrast
+
+### 5. Testing
+- Write comprehensive unit tests
+- Test edge cases
+- Mock external dependencies
From b00165bb73364d2fc909ea8c4f2cee4f97895965 Mon Sep 17 00:00:00 2001
From: ahmadtaimoor-deriv
<129935294+ahmadtaimoor-deriv@users.noreply.github.com>
Date: Tue, 4 Feb 2025 13:35:22 +0800
Subject: [PATCH 5/7] readme fix
---
src/components/BottomSheet/README.md | 167 +++++++++++++++++++++
src/components/README.md | 217 ++++++++++++---------------
2 files changed, 263 insertions(+), 121 deletions(-)
create mode 100644 src/components/BottomSheet/README.md
diff --git a/src/components/BottomSheet/README.md b/src/components/BottomSheet/README.md
new file mode 100644
index 0000000..44a58a5
--- /dev/null
+++ b/src/components/BottomSheet/README.md
@@ -0,0 +1,167 @@
+# BottomSheet Component
+
+## Overview
+A bottom sheet component that slides up from the bottom of the screen with drag-to-dismiss functionality. Uses Zustand for state management and a configuration-based approach for content.
+
+## Internal Working
+
+### 1. State Management
+```typescript
+// bottomSheetStore.ts
+interface BottomSheetState {
+ showBottomSheet: boolean; // Controls visibility
+ key: string | null; // Content identifier
+ height: string; // Sheet height
+ setBottomSheet: (show: boolean, key?: string, height?: string) => void;
+}
+```
+
+The component uses Zustand to maintain a single source of truth for:
+- Visibility state
+- Current content key
+- Sheet height
+
+### 2. Content Configuration
+```typescript
+// bottomSheetConfig.tsx
+interface BottomSheetConfig {
+ [key: string]: {
+ body: ReactNode;
+ }
+}
+```
+
+Content is configured through a central config file, allowing for:
+- Reusable content definitions
+- Type-safe content management
+- Easy content updates
+
+### 3. Gesture Handling
+
+#### Touch Start
+```typescript
+const handleTouchStart = (e: React.TouchEvent) => {
+ dragStartY.current = e.touches[0].clientY;
+ currentY.current = 0;
+ isDragging.current = true;
+}
+```
+- Captures initial touch position
+- Sets up dragging state
+
+#### Touch Move
+```typescript
+const handleTouchMove = (e: TouchEvent) => {
+ if (!isDragging.current) return;
+
+ const deltaY = e.touches[0].clientY - dragStartY.current;
+ if (deltaY > 0) {
+ sheetRef.current.style.transform = `translateY(${deltaY}px)`;
+ }
+}
+```
+- Calculates drag distance
+- Updates sheet position
+- Only allows downward dragging
+
+#### Touch End
+```typescript
+const handleTouchEnd = () => {
+ if (currentY.current > 100) {
+ setBottomSheet(false);
+ }
+ // Reset position and state
+}
+```
+- Checks if drag distance exceeds threshold
+- Closes sheet if threshold met
+- Resets position and state
+
+### 4. Event Cleanup
+```typescript
+useEffect(() => {
+ if (showBottomSheet) {
+ document.addEventListener("touchmove", handleTouchMove);
+ document.addEventListener("touchend", handleTouchEnd);
+
+ return () => {
+ document.removeEventListener("touchmove", handleTouchMove);
+ document.removeEventListener("touchend", handleTouchEnd);
+ };
+ }
+}, [showBottomSheet, handleTouchMove, handleTouchEnd]);
+```
+- Adds event listeners when sheet is shown
+- Removes listeners when sheet is closed
+- Prevents memory leaks
+
+### 5. Height Management
+```typescript
+const processedHeight = height.endsWith('%')
+ ? `${parseFloat(height)}vh`
+ : height;
+```
+Supports multiple height formats:
+- Percentage (converted to vh)
+- Pixels
+- Viewport height
+
+### 6. Styling
+Uses Tailwind CSS variables for theme-aware styling:
+```tsx
+
// Theme background
+
// Theme muted color
+```
+
+## Usage Example
+
+```tsx
+// 1. Configure content
+// bottomSheetConfig.tsx
+export const bottomSheetConfig = {
+ 'trade-options': {
+ body:
+ }
+};
+
+// 2. Place component
+// App.tsx
+
+
+// 3. Control sheet
+// AnyComponent.tsx
+const { setBottomSheet } = useBottomSheetStore();
+
+// Open with 50% height
+setBottomSheet(true, 'trade-options', '50%');
+
+// Close
+setBottomSheet(false);
+```
+
+## Key Features
+
+1. **Single Instance**
+ - One bottom sheet instance for entire app
+ - Content switched through configuration
+ - Prevents multiple sheets
+
+2. **Gesture Support**
+ - Drag to dismiss
+ - Smooth animations
+ - Touch event cleanup
+
+3. **Theme Integration**
+ - Uses Tailwind CSS variables
+ - Dark mode support
+ - Consistent styling
+
+4. **Dynamic Height**
+ - Percentage values
+ - Pixel values
+ - Viewport height
+
+5. **Performance**
+ - Event listener cleanup
+ - Optimized re-renders
+ - Efficient state updates
diff --git a/src/components/README.md b/src/components/README.md
index e6b8da3..751fd67 100644
--- a/src/components/README.md
+++ b/src/components/README.md
@@ -1,127 +1,102 @@
-# Components Documentation
-
-## Table of Contents
-- [BottomSheet](#bottomsheet)
-- [TradeParam](#tradeparam)
-- [TradeButton](#tradebutton)
-- [Chart](#chart)
-- [DurationOptions](#durationoptions)
-- [AddMarketButton](#addmarketbutton)
-
-## BottomSheet
-
-A reusable bottom sheet component with drag-to-dismiss functionality.
-
-### Key Features
-- Single instance pattern using Zustand store
-- Dynamic height support (%, px, vh)
-- Theme-aware using Tailwind CSS variables
-- Drag gesture support with proper event cleanup
-- Content management through configuration
-
-### Usage
-
-```tsx
-// 1. Configure content in bottomSheetConfig.tsx
-export const bottomSheetConfig = {
- 'my-key': {
- body:
- }
-};
-
-// 2. Place component at root level
-
-
-// 3. Control from anywhere using the store
-const { setBottomSheet } = useBottomSheetStore();
-setBottomSheet(true, 'my-key', '50%');
-```
-
-### State Management
-```typescript
-interface BottomSheetState {
- showBottomSheet: boolean;
- key: string | null;
- height: string;
- setBottomSheet: (show: boolean, key?: string, height?: string) => void;
-}
-```
+# Component Architecture
-### Recent Changes
-- Removed click-outside-to-close behavior
-- Added proper event listener cleanup
-- Migrated to theme-aware colors using Tailwind CSS variables
-- Improved touch event handling
-- Added dynamic height support
+This directory contains React components following Atomic Component Design principles. The components are organized to be modular, self-contained, and independently testable.
-## TradeParam
+## Component Organization
-A card component for displaying trade parameters.
-
-### Usage
-```tsx
-
```
-
-### Props
-```typescript
-interface TradeParamProps {
- label: string;
- value: string;
- onClick?: () => void;
-}
+components/
+├── AddMarketButton/ # Market selection functionality
+├── BottomNav/ # Bottom navigation bar
+├── BottomSheet/ # Bottom sheet
+├── Chart/ # Trading chart visualization
+├── DurationOptions/ # Trade duration selection
+├── TradeButton/ # Trade execution controls
+├── TradeFields/ # Trade parameter inputs
+└── ui/ # Shared UI components
```
-### Recent Changes
-- Made component purely presentational
-- Added optional onClick handler
-- Cursor pointer only shows when onClick is provided
-
-## TradeButton
-
-[Documentation for TradeButton component]
-
-## Chart
-
-[Documentation for Chart component]
-
-## DurationOptions
-
-[Documentation for DurationOptions component]
-
-## AddMarketButton
-
-[Documentation for AddMarketButton component]
-
----
-
-## Component Design Principles
-
-### 1. State Management
-- Use Zustand for global state
-- Keep components as pure as possible
-- Pass event handlers from parent components
-
-### 2. Styling
-- Use Tailwind CSS with theme variables
-- Follow design system color tokens
-- Ensure dark mode compatibility
-
-### 3. Performance
-- Implement proper cleanup
-- Use React.memo where beneficial
-- Lazy load when appropriate
-
-### 4. Accessibility
-- Follow WCAG guidelines
-- Ensure proper keyboard navigation
-- Maintain appropriate color contrast
-
-### 5. Testing
-- Write comprehensive unit tests
-- Test edge cases
-- Mock external dependencies
+## Design Principles
+
+1. **Atomic Design**
+ - Components are built from smallest to largest
+ - Each component has a single responsibility
+ - Components are self-contained with their own styles and logic
+
+2. **Styling**
+ - Uses TailwindCSS for consistent styling
+ - Styles are encapsulated within components
+ - Follows utility-first CSS principles
+
+3. **State Management**
+ - Local state for component-specific logic
+ - Zustand for shared/global state
+ - Props for component configuration
+
+4. **Testing**
+ - Each component has its own test suite
+ - Tests cover component rendering and interactions
+ - Mock external dependencies when needed
+
+## Component Guidelines
+
+1. **File Structure**
+ ```
+ ComponentName/
+ ├── ComponentName.tsx # Main component implementation
+ ├── index.ts # Public exports
+ └── __tests__/ # Test files
+ └── ComponentName.test.tsx
+ ```
+
+2. **Component Implementation**
+ ```typescript
+ import { useState } from 'react';
+ import { cn } from '@/lib/utils';
+
+ interface ComponentProps {
+ // Props interface
+ }
+
+ export function Component({ ...props }: ComponentProps) {
+ // Implementation
+ }
+ ```
+
+3. **Testing Pattern**
+ ```typescript
+ import { render, screen } from '@testing-library/react';
+ import { Component } from './Component';
+
+ describe('Component', () => {
+ it('should render correctly', () => {
+ render(
);
+ // Assertions
+ });
+ });
+ ```
+
+## Best Practices
+
+1. **Component Design**
+ - Keep components focused and single-purpose
+ - Use TypeScript interfaces for props
+ - Implement proper error boundaries
+ - Handle loading and error states
+
+2. **Performance**
+ - Implement lazy loading where appropriate
+ - Memoize expensive calculations
+ - Optimize re-renders using React.memo when needed
+
+3. **Accessibility**
+ - Use semantic HTML elements
+ - Include ARIA attributes where necessary
+ - Ensure keyboard navigation support
+ - Maintain proper color contrast
+
+4. **Code Quality**
+ - Follow consistent naming conventions
+ - Document complex logic
+ - Write comprehensive tests
+ - Use proper TypeScript types
From 486ae856ec00d5340851da06a5299b7707100e55 Mon Sep 17 00:00:00 2001
From: ahmadtaimoor-deriv
<129935294+ahmadtaimoor-deriv@users.noreply.github.com>
Date: Tue, 4 Feb 2025 13:46:47 +0800
Subject: [PATCH 6/7] adding refs in llms.txt
---
llms.txt | 1 +
1 file changed, 1 insertion(+)
diff --git a/llms.txt b/llms.txt
index c410ab6..66470a4 100644
--- a/llms.txt
+++ b/llms.txt
@@ -14,6 +14,7 @@ The application is structured around modular, self-contained components and uses
## Architecture
- [Component Structure](src/components/README.md): Details on Atomic Component Design implementation and component organization
+- [Bottom Sheet Store](src/stores/bottomSheetStore.ts): Centralized bottom sheet state management
- [WebSocket Architecture](src/services/api/websocket/README.md): Comprehensive documentation of the WebSocket implementation for real-time market data and contract pricing
- [State Management](src/stores/README.md): Information about Zustand store implementation and state management patterns
From 90eb2edbfd44c35d53dd3b03f93ed32e408c62d0 Mon Sep 17 00:00:00 2001
From: ahmadtaimoor-deriv
<129935294+ahmadtaimoor-deriv@users.noreply.github.com>
Date: Tue, 4 Feb 2025 14:14:08 +0800
Subject: [PATCH 7/7] add onDragDown callback
---
src/components/BottomSheet/BottomSheet.tsx | 5 +-
src/components/BottomSheet/README.md | 209 ++++++++-------------
src/stores/bottomSheetStore.ts | 11 +-
3 files changed, 92 insertions(+), 133 deletions(-)
diff --git a/src/components/BottomSheet/BottomSheet.tsx b/src/components/BottomSheet/BottomSheet.tsx
index 29fa8c9..bdd6cab 100644
--- a/src/components/BottomSheet/BottomSheet.tsx
+++ b/src/components/BottomSheet/BottomSheet.tsx
@@ -3,7 +3,7 @@ import { useBottomSheetStore } from "@/stores/bottomSheetStore";
import { bottomSheetConfig } from "@/config/bottomSheetConfig";
export const BottomSheet = () => {
- const { showBottomSheet, key, height, setBottomSheet } = useBottomSheetStore();
+ const { showBottomSheet, key, height, onDragDown, setBottomSheet } = useBottomSheetStore();
const sheetRef = useRef
(null);
const dragStartY = useRef(0);
@@ -26,8 +26,9 @@ export const BottomSheet = () => {
if (deltaY > 0) {
sheetRef.current.style.transform = `translateY(${deltaY}px)`;
+ onDragDown?.();
}
- }, []);
+ }, [onDragDown]);
const handleTouchEnd = useCallback(() => {
if (!sheetRef.current) return;
diff --git a/src/components/BottomSheet/README.md b/src/components/BottomSheet/README.md
index 44a58a5..d68bb0e 100644
--- a/src/components/BottomSheet/README.md
+++ b/src/components/BottomSheet/README.md
@@ -1,167 +1,122 @@
# BottomSheet Component
## Overview
-A bottom sheet component that slides up from the bottom of the screen with drag-to-dismiss functionality. Uses Zustand for state management and a configuration-based approach for content.
+A reusable bottom sheet component with drag-to-dismiss functionality and drag callback support.
-## Internal Working
+## Features
+- Single instance pattern using Zustand store
+- Dynamic height support (%, px, vh)
+- Theme-aware using Tailwind CSS variables
+- Drag gesture support with callback
+- Content management through configuration
+
+## Usage
+
+### Basic Usage
+```tsx
+const { setBottomSheet } = useBottomSheetStore();
+
+// Show bottom sheet
+setBottomSheet(true, 'content-key', '50%');
+
+// Hide bottom sheet
+setBottomSheet(false);
+```
+
+### With Drag Callback
+```tsx
+const handleDragDown = () => {
+ console.log('Bottom sheet is being dragged down');
+ // Your drag down logic here
+};
+
+setBottomSheet(true, 'content-key', '50%', handleDragDown);
+```
+
+## State Management
-### 1. State Management
```typescript
-// bottomSheetStore.ts
interface BottomSheetState {
showBottomSheet: boolean; // Controls visibility
key: string | null; // Content identifier
height: string; // Sheet height
- setBottomSheet: (show: boolean, key?: string, height?: string) => void;
+ onDragDown?: () => void; // Optional drag callback
+ setBottomSheet: (
+ show: boolean,
+ key?: string,
+ height?: string,
+ onDragDown?: () => void
+ ) => void;
}
```
-The component uses Zustand to maintain a single source of truth for:
-- Visibility state
-- Current content key
-- Sheet height
+## Height Support
+- Percentage: '50%' (converted to vh)
+- Pixels: '380px'
+- Viewport height: '75vh'
-### 2. Content Configuration
-```typescript
-// bottomSheetConfig.tsx
-interface BottomSheetConfig {
- [key: string]: {
- body: ReactNode;
- }
-}
-```
+## Gesture Handling
-Content is configured through a central config file, allowing for:
-- Reusable content definitions
-- Type-safe content management
-- Easy content updates
+### Drag to Dismiss
+- Drag down on handle bar to dismiss
+- Threshold: 100px vertical distance
+- Smooth animation on release
+- Optional callback during drag
-### 3. Gesture Handling
+### Event Cleanup
+- Event listeners added only when sheet is shown
+- Proper cleanup on sheet close and unmount
-#### Touch Start
-```typescript
-const handleTouchStart = (e: React.TouchEvent) => {
- dragStartY.current = e.touches[0].clientY;
- currentY.current = 0;
- isDragging.current = true;
-}
+## Styling
+Uses Tailwind CSS variables for theme support:
+```tsx
+className="bg-background" // Theme background
+className="bg-muted" // Theme muted color
```
-- Captures initial touch position
-- Sets up dragging state
-#### Touch Move
+## Implementation Details
+
+### Touch Event Handling
```typescript
const handleTouchMove = (e: TouchEvent) => {
if (!isDragging.current) return;
-
+
const deltaY = e.touches[0].clientY - dragStartY.current;
if (deltaY > 0) {
+ // Update sheet position
sheetRef.current.style.transform = `translateY(${deltaY}px)`;
+ // Call drag callback if provided
+ onDragDown?.();
}
-}
-```
-- Calculates drag distance
-- Updates sheet position
-- Only allows downward dragging
-
-#### Touch End
-```typescript
-const handleTouchEnd = () => {
- if (currentY.current > 100) {
- setBottomSheet(false);
- }
- // Reset position and state
-}
-```
-- Checks if drag distance exceeds threshold
-- Closes sheet if threshold met
-- Resets position and state
-
-### 4. Event Cleanup
-```typescript
-useEffect(() => {
- if (showBottomSheet) {
- document.addEventListener("touchmove", handleTouchMove);
- document.addEventListener("touchend", handleTouchEnd);
-
- return () => {
- document.removeEventListener("touchmove", handleTouchMove);
- document.removeEventListener("touchend", handleTouchEnd);
- };
- }
-}, [showBottomSheet, handleTouchMove, handleTouchEnd]);
+};
```
-- Adds event listeners when sheet is shown
-- Removes listeners when sheet is closed
-- Prevents memory leaks
-### 5. Height Management
+### Height Processing
```typescript
const processedHeight = height.endsWith('%')
? `${parseFloat(height)}vh`
: height;
```
-Supports multiple height formats:
-- Percentage (converted to vh)
-- Pixels
-- Viewport height
-
-### 6. Styling
-Uses Tailwind CSS variables for theme-aware styling:
-```tsx
- // Theme background
-
// Theme muted color
-```
-## Usage Example
+## Example
```tsx
-// 1. Configure content
-// bottomSheetConfig.tsx
-export const bottomSheetConfig = {
- 'trade-options': {
- body:
- }
-};
-
-// 2. Place component
-// App.tsx
-
-
-// 3. Control sheet
-// AnyComponent.tsx
-const { setBottomSheet } = useBottomSheetStore();
-
-// Open with 50% height
-setBottomSheet(true, 'trade-options', '50%');
+import { useBottomSheetStore } from "@/stores/bottomSheetStore";
-// Close
-setBottomSheet(false);
-```
-
-## Key Features
+function MyComponent() {
+ const { setBottomSheet } = useBottomSheetStore();
-1. **Single Instance**
- - One bottom sheet instance for entire app
- - Content switched through configuration
- - Prevents multiple sheets
+ const handleDragDown = () => {
+ // Handle drag down event
+ };
-2. **Gesture Support**
- - Drag to dismiss
- - Smooth animations
- - Touch event cleanup
+ const showSheet = () => {
+ setBottomSheet(true, 'my-content', '50%', handleDragDown);
+ };
-3. **Theme Integration**
- - Uses Tailwind CSS variables
- - Dark mode support
- - Consistent styling
-
-4. **Dynamic Height**
- - Percentage values
- - Pixel values
- - Viewport height
-
-5. **Performance**
- - Event listener cleanup
- - Optimized re-renders
- - Efficient state updates
+ return (
+
+ );
+}
diff --git a/src/stores/bottomSheetStore.ts b/src/stores/bottomSheetStore.ts
index 5f89a56..a8f3278 100644
--- a/src/stores/bottomSheetStore.ts
+++ b/src/stores/bottomSheetStore.ts
@@ -4,16 +4,19 @@ interface BottomSheetState {
showBottomSheet: boolean;
key: string | null;
height: string;
- setBottomSheet: (show: boolean, key?: string, height?: string) => void;
+ onDragDown?: () => void;
+ setBottomSheet: (show: boolean, key?: string, height?: string, onDragDown?: () => void) => void;
}
export const useBottomSheetStore = create((set) => ({
showBottomSheet: false,
key: null,
- height: '400px',
- setBottomSheet: (show: boolean, key?: string, height?: string) => set({
+ height: '380px',
+ onDragDown: undefined,
+ setBottomSheet: (show: boolean, key?: string, height?: string, onDragDown?: () => void) => set({
showBottomSheet: show,
key: show ? key || null : null,
- height: height || '400px'
+ height: height || '380px',
+ onDragDown: onDragDown
}),
}));