heartraite 1.0.18 → 1.0.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/dist/enum/cloudfunction.enum.d.ts +1 -0
- package/dist/enum/cloudfunction.enum.js +1 -0
- package/dist/hooks/index.d.ts +1 -0
- package/dist/hooks/index.js +17 -0
- package/dist/hooks/useTypewriter.d.ts +12 -0
- package/dist/hooks/useTypewriter.js +61 -0
- package/dist/hooks/useTypewriter.test.d.ts +1 -0
- package/dist/hooks/useTypewriter.test.js +71 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/types/cloud-function-types.d.ts +6 -2
- package/dist/types/request.types.d.ts +1 -0
- package/dist/types/response.types.d.ts +1 -0
- package/jest.config.js +9 -0
- package/package.json +15 -3
- package/src/enum/cloudfunction.enum.ts +1 -0
- package/src/hooks/index.ts +1 -0
- package/src/hooks/useTypewriter.test.ts +86 -0
- package/src/hooks/useTypewriter.ts +78 -0
- package/src/index.ts +1 -0
- package/src/types/cloud-function-types.ts +6 -0
- package/src/types/request.types.ts +1 -0
- package/src/types/response.types.ts +1 -0
|
@@ -2,6 +2,7 @@ export declare enum CloudFunction {
|
|
|
2
2
|
REGISTER_USER = "auth/register-user",
|
|
3
3
|
AUTHENTICATE_BANKID = "bankid/authenticate",
|
|
4
4
|
COLLECT_BANKID = "bankid/collect",
|
|
5
|
+
GET_CA = "ca/get-ca",
|
|
5
6
|
SUBMIT_ANSWER = "ca/submit-answer",
|
|
6
7
|
SUBMIT_CATEGORY_FEEDBACK = "ca/submit-category-feedback",
|
|
7
8
|
CREATE_LIKE = "like/create-like",
|
|
@@ -6,6 +6,7 @@ var CloudFunction;
|
|
|
6
6
|
CloudFunction["REGISTER_USER"] = "auth/register-user";
|
|
7
7
|
CloudFunction["AUTHENTICATE_BANKID"] = "bankid/authenticate";
|
|
8
8
|
CloudFunction["COLLECT_BANKID"] = "bankid/collect";
|
|
9
|
+
CloudFunction["GET_CA"] = "ca/get-ca";
|
|
9
10
|
CloudFunction["SUBMIT_ANSWER"] = "ca/submit-answer";
|
|
10
11
|
CloudFunction["SUBMIT_CATEGORY_FEEDBACK"] = "ca/submit-category-feedback";
|
|
11
12
|
CloudFunction["CREATE_LIKE"] = "like/create-like";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./useTypewriter";
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
__exportStar(require("./useTypewriter"), exports);
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
interface UseTypewriterOptions {
|
|
2
|
+
text: string;
|
|
3
|
+
typingSpeed?: number;
|
|
4
|
+
initialDelay?: number;
|
|
5
|
+
blinkingSpeed?: number;
|
|
6
|
+
}
|
|
7
|
+
export declare const useTypewriter: ({ text, typingSpeed, initialDelay, blinkingSpeed, }: UseTypewriterOptions) => {
|
|
8
|
+
displayedText: string;
|
|
9
|
+
showDot: boolean;
|
|
10
|
+
isComplete: boolean;
|
|
11
|
+
};
|
|
12
|
+
export {};
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.useTypewriter = void 0;
|
|
4
|
+
const react_1 = require("react");
|
|
5
|
+
const useTypewriter = ({ text, typingSpeed = 50, initialDelay = 1000, blinkingSpeed = 500, }) => {
|
|
6
|
+
const [isComplete, setIsComplete] = (0, react_1.useState)(false);
|
|
7
|
+
const [displayedText, setDisplayedText] = (0, react_1.useState)("");
|
|
8
|
+
const [showDot, setShowDot] = (0, react_1.useState)(false);
|
|
9
|
+
(0, react_1.useEffect)(() => {
|
|
10
|
+
if (!text || text.length === 0) {
|
|
11
|
+
setDisplayedText(""); // No text to display
|
|
12
|
+
setIsComplete(true); // Mark typing as complete immediately
|
|
13
|
+
setShowDot(false); // No blinking dot for empty text
|
|
14
|
+
return; // Exit early if text is empty
|
|
15
|
+
}
|
|
16
|
+
let blinkingInterval = null;
|
|
17
|
+
let typingTimeout = null;
|
|
18
|
+
// Initialize state
|
|
19
|
+
setDisplayedText(""); // Clear the displayed text
|
|
20
|
+
setShowDot(true); // Start with a blinking dot
|
|
21
|
+
setIsComplete(false); // Reset completion flag
|
|
22
|
+
// Start blinking dot effect
|
|
23
|
+
blinkingInterval = setInterval(() => {
|
|
24
|
+
setShowDot((prev) => !prev);
|
|
25
|
+
}, blinkingSpeed);
|
|
26
|
+
// Start typing after the initial delay
|
|
27
|
+
typingTimeout = setTimeout(() => {
|
|
28
|
+
if (blinkingInterval) {
|
|
29
|
+
clearInterval(blinkingInterval); // Stop blinking before typing
|
|
30
|
+
setShowDot(false); // Hide blinking dot
|
|
31
|
+
}
|
|
32
|
+
// Typing effect
|
|
33
|
+
let currentIndex = 0;
|
|
34
|
+
const typeNextChar = () => {
|
|
35
|
+
if (currentIndex < text.length) {
|
|
36
|
+
const charToAdd = text[currentIndex];
|
|
37
|
+
setDisplayedText((prev) => prev + charToAdd); // Append character
|
|
38
|
+
currentIndex++;
|
|
39
|
+
setTimeout(typeNextChar, typingSpeed); // Schedule next character
|
|
40
|
+
}
|
|
41
|
+
else {
|
|
42
|
+
setIsComplete(true); // Mark as complete
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
// Trigger the first character manually
|
|
46
|
+
setDisplayedText(text.charAt(0));
|
|
47
|
+
currentIndex = 1;
|
|
48
|
+
// Schedule the rest of the characters
|
|
49
|
+
setTimeout(typeNextChar, typingSpeed);
|
|
50
|
+
}, initialDelay);
|
|
51
|
+
// Cleanup on unmount or dependencies change
|
|
52
|
+
return () => {
|
|
53
|
+
if (blinkingInterval)
|
|
54
|
+
clearInterval(blinkingInterval);
|
|
55
|
+
if (typingTimeout)
|
|
56
|
+
clearTimeout(typingTimeout);
|
|
57
|
+
};
|
|
58
|
+
}, [text, typingSpeed, initialDelay, blinkingSpeed]);
|
|
59
|
+
return { displayedText, showDot, isComplete };
|
|
60
|
+
};
|
|
61
|
+
exports.useTypewriter = useTypewriter;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const react_1 = require("@testing-library/react");
|
|
4
|
+
const useTypewriter_1 = require("./useTypewriter");
|
|
5
|
+
jest.useFakeTimers();
|
|
6
|
+
describe("useTypewriter", () => {
|
|
7
|
+
const sampleText = "Hello, world!";
|
|
8
|
+
const defaultOptions = {
|
|
9
|
+
text: sampleText,
|
|
10
|
+
typingSpeed: 50,
|
|
11
|
+
initialDelay: 500,
|
|
12
|
+
blinkingSpeed: 300,
|
|
13
|
+
};
|
|
14
|
+
it("should start with an empty displayedText", () => {
|
|
15
|
+
const { result } = (0, react_1.renderHook)(() => (0, useTypewriter_1.useTypewriter)(defaultOptions));
|
|
16
|
+
expect(result.current.displayedText).toBe("");
|
|
17
|
+
expect(result.current.isComplete).toBe(false);
|
|
18
|
+
expect(result.current.showDot).toBe(true);
|
|
19
|
+
});
|
|
20
|
+
it("should handle empty text input gracefully", () => {
|
|
21
|
+
const { result } = (0, react_1.renderHook)(() => (0, useTypewriter_1.useTypewriter)({ ...defaultOptions, text: "" }));
|
|
22
|
+
// Assert initial state for empty text input
|
|
23
|
+
expect(result.current.displayedText).toBe(""); // Should be an empty string
|
|
24
|
+
expect(result.current.isComplete).toBe(true); // Typing is complete immediately
|
|
25
|
+
expect(result.current.showDot).toBe(false); // No blinking dot
|
|
26
|
+
});
|
|
27
|
+
it("should display the correct text after typing is complete", async () => {
|
|
28
|
+
const text = "Du beskriver dig själv som en person som värderar kroppstyper, engagemang, kompetens i livet.";
|
|
29
|
+
const { result } = (0, react_1.renderHook)(() => (0, useTypewriter_1.useTypewriter)({
|
|
30
|
+
...defaultOptions,
|
|
31
|
+
text,
|
|
32
|
+
}));
|
|
33
|
+
// Simulate the initial delay (before typing starts)
|
|
34
|
+
(0, react_1.act)(() => {
|
|
35
|
+
jest.advanceTimersByTime(defaultOptions.initialDelay);
|
|
36
|
+
});
|
|
37
|
+
// Simulate the full typing duration (based on the text length and typing speed)
|
|
38
|
+
const totalTypingTime = text.length * defaultOptions.typingSpeed;
|
|
39
|
+
(0, react_1.act)(() => {
|
|
40
|
+
jest.advanceTimersByTime(totalTypingTime);
|
|
41
|
+
});
|
|
42
|
+
// Assert that after typing is complete, the correct text is displayed
|
|
43
|
+
expect(result.current.displayedText).toBe(text);
|
|
44
|
+
expect(result.current.isComplete).toBe(true);
|
|
45
|
+
expect(result.current.showDot).toBe(false); // No more blinking dot
|
|
46
|
+
});
|
|
47
|
+
it("should respect typing speed and initial delay", () => {
|
|
48
|
+
const customOptions = {
|
|
49
|
+
...defaultOptions,
|
|
50
|
+
typingSpeed: 100,
|
|
51
|
+
initialDelay: 1000,
|
|
52
|
+
};
|
|
53
|
+
const { result } = (0, react_1.renderHook)(() => (0, useTypewriter_1.useTypewriter)(customOptions));
|
|
54
|
+
// Before initial delay
|
|
55
|
+
(0, react_1.act)(() => {
|
|
56
|
+
jest.advanceTimersByTime(900);
|
|
57
|
+
});
|
|
58
|
+
expect(result.current.displayedText).toBe("");
|
|
59
|
+
expect(result.current.isComplete).toBe(false);
|
|
60
|
+
// After initial delay
|
|
61
|
+
(0, react_1.act)(() => {
|
|
62
|
+
jest.advanceTimersByTime(100);
|
|
63
|
+
});
|
|
64
|
+
expect(result.current.displayedText).toBe(sampleText.charAt(0));
|
|
65
|
+
// Verify typing speed
|
|
66
|
+
(0, react_1.act)(() => {
|
|
67
|
+
jest.advanceTimersByTime(customOptions.typingSpeed);
|
|
68
|
+
});
|
|
69
|
+
expect(result.current.displayedText).toBe(sampleText.slice(0, 2));
|
|
70
|
+
});
|
|
71
|
+
});
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { CloudFunction } from "../enum";
|
|
2
|
-
import { AddImagesRequest, AuthenticateBankIDRequest, CollectBankIDRequest, CreateBillingPortalRequest, CreateCheckoutSessionRequest, CreateLikeRequest, CreatePLSAssessmentRequest, CreatePLSInputRequest, CreatePLSSubmissionRequest, CreateReportRequest, GetMatchableUserRequest, GetMatchCheckRequest, GetMatchesRequest, GetMatchRequest, GetMessagesRequest, GetPLSAssessmentRequest, GetPLSSubmissionRequest, GetProductsRequest, GetUserRequest, HandleLikeSeenRequest, HandleMatchSeenRequest, RegisterOnboardingRequest, RegisterUserRequest, RemoveMatchRequest, SendMessageRequest, SubmitAnswerRequest, SubmitCategoryFeedbackRequest, SubmitPLSCategoryFeedbackRequest, UpdatePLSAnswerRequest, UpdateQuestionsRequest, UpdateUserRequest } from "./request.types";
|
|
3
|
-
import { AddImagesResponse, AuthenticateBankIDResponse, CollectBankIDResponse, CreateBillingPortalResponse, CreateCheckoutSessionResponse, CreateLikeResponse, CreatePLSAssessmentResponse, CreatePLSInputResponse, CreatePLSSubmissionResponse, CreateReportResponse, GetMatchableUserResponse, GetMatchableUsersResponse, GetMatchCheckResponse, GetMatchesResponse, GetMatchResponse, GetMessagesResponse, GetPLSAssessmentResponse, GetPLSAssessmentsResponse, GetPLSSubmissionResponse, GetProductsResponse, GetUserResponse, HandleLikeSeenResponse, HandleMatchSeenResponse, RegisterOnboardingResponse, RegisterUserResponse, RemoveMatchResponse, SendMessageResponse, SubmitAnswerResponse, SubmitCategoryFeedbackResponse, SubmitPLSCategoryFeedbackResponse, UpdatePLSAnswerResponse, UpdateQuestionsResponse, UpdateUserResponse } from "./response.types";
|
|
2
|
+
import { AddImagesRequest, AuthenticateBankIDRequest, CollectBankIDRequest, CreateBillingPortalRequest, CreateCheckoutSessionRequest, CreateLikeRequest, CreatePLSAssessmentRequest, CreatePLSInputRequest, CreatePLSSubmissionRequest, CreateReportRequest, GetCARequest, GetMatchableUserRequest, GetMatchCheckRequest, GetMatchesRequest, GetMatchRequest, GetMessagesRequest, GetPLSAssessmentRequest, GetPLSSubmissionRequest, GetProductsRequest, GetUserRequest, HandleLikeSeenRequest, HandleMatchSeenRequest, RegisterOnboardingRequest, RegisterUserRequest, RemoveMatchRequest, SendMessageRequest, SubmitAnswerRequest, SubmitCategoryFeedbackRequest, SubmitPLSCategoryFeedbackRequest, UpdatePLSAnswerRequest, UpdateQuestionsRequest, UpdateUserRequest } from "./request.types";
|
|
3
|
+
import { AddImagesResponse, AuthenticateBankIDResponse, CollectBankIDResponse, CreateBillingPortalResponse, CreateCheckoutSessionResponse, CreateLikeResponse, CreatePLSAssessmentResponse, CreatePLSInputResponse, CreatePLSSubmissionResponse, CreateReportResponse, GetCAResponse, GetMatchableUserResponse, GetMatchableUsersResponse, GetMatchCheckResponse, GetMatchesResponse, GetMatchResponse, GetMessagesResponse, GetPLSAssessmentResponse, GetPLSAssessmentsResponse, GetPLSSubmissionResponse, GetProductsResponse, GetUserResponse, HandleLikeSeenResponse, HandleMatchSeenResponse, RegisterOnboardingResponse, RegisterUserResponse, RemoveMatchResponse, SendMessageResponse, SubmitAnswerResponse, SubmitCategoryFeedbackResponse, SubmitPLSCategoryFeedbackResponse, UpdatePLSAnswerResponse, UpdateQuestionsResponse, UpdateUserResponse } from "./response.types";
|
|
4
4
|
export type CloudFunctionTypes = {
|
|
5
5
|
[CloudFunction.REGISTER_USER]: {
|
|
6
6
|
payload: RegisterUserRequest;
|
|
@@ -14,6 +14,10 @@ export type CloudFunctionTypes = {
|
|
|
14
14
|
payload: CollectBankIDRequest;
|
|
15
15
|
response: CollectBankIDResponse;
|
|
16
16
|
};
|
|
17
|
+
[CloudFunction.GET_CA]: {
|
|
18
|
+
payload: GetCARequest;
|
|
19
|
+
response: GetCAResponse;
|
|
20
|
+
};
|
|
17
21
|
[CloudFunction.SUBMIT_ANSWER]: {
|
|
18
22
|
payload: SubmitAnswerRequest;
|
|
19
23
|
response: SubmitAnswerResponse;
|
package/jest.config.js
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
module.exports = {
|
|
2
|
+
preset: "ts-jest", // Use ts-jest preset to handle TypeScript
|
|
3
|
+
testEnvironment: "jest-environment-jsdom", // Use jsdom for React tests
|
|
4
|
+
moduleFileExtensions: ["js", "jsx", "ts", "tsx"], // Support for js, jsx, ts, tsx files
|
|
5
|
+
testMatch: [
|
|
6
|
+
"**/*.test.[jt]s?(x)", // Match any .test.js, .test.ts, .test.jsx, .test.tsx files
|
|
7
|
+
],
|
|
8
|
+
collectCoverageFrom: ["src/**/*.{js,jsx,ts,tsx}", "!src/**/*.d.ts"], // Collect coverage for all files except d.ts
|
|
9
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "heartraite",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.20",
|
|
4
4
|
"description": "Heartraite npm package for common functionality",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -20,12 +20,24 @@
|
|
|
20
20
|
"url": "https://github.com/agottfredsson/heartraite-npm/issues"
|
|
21
21
|
},
|
|
22
22
|
"homepage": "https://github.com/agottfredsson/heartraite-npm#readme",
|
|
23
|
+
"dependencies": {
|
|
24
|
+
"react": "^19.0.0",
|
|
25
|
+
"react-dom": "^19.0.0"
|
|
26
|
+
},
|
|
23
27
|
"peerDependencies": {
|
|
24
28
|
"firebase": "^11.0.1"
|
|
25
29
|
},
|
|
26
30
|
"devDependencies": {
|
|
31
|
+
"@testing-library/dom": "^10.4.0",
|
|
32
|
+
"@testing-library/react": "^16.1.0",
|
|
33
|
+
"@types/jest": "^29.5.14",
|
|
27
34
|
"@types/node": "^22.9.0",
|
|
28
|
-
"
|
|
29
|
-
"
|
|
35
|
+
"@types/react": "^19.0.2",
|
|
36
|
+
"@types/react-dom": "^19.0.2",
|
|
37
|
+
"firebase": "^11.0.1",
|
|
38
|
+
"jest": "^29.7.0",
|
|
39
|
+
"jest-environment-jsdom": "^29.7.0",
|
|
40
|
+
"ts-jest": "^29.2.5",
|
|
41
|
+
"typescript": "^5.6.3"
|
|
30
42
|
}
|
|
31
43
|
}
|
|
@@ -2,6 +2,7 @@ export enum CloudFunction {
|
|
|
2
2
|
REGISTER_USER = "auth/register-user",
|
|
3
3
|
AUTHENTICATE_BANKID = "bankid/authenticate",
|
|
4
4
|
COLLECT_BANKID = "bankid/collect",
|
|
5
|
+
GET_CA = "ca/get-ca",
|
|
5
6
|
SUBMIT_ANSWER = "ca/submit-answer",
|
|
6
7
|
SUBMIT_CATEGORY_FEEDBACK = "ca/submit-category-feedback",
|
|
7
8
|
CREATE_LIKE = "like/create-like",
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./useTypewriter";
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { renderHook, act } from "@testing-library/react";
|
|
2
|
+
import { useTypewriter } from "./useTypewriter";
|
|
3
|
+
|
|
4
|
+
jest.useFakeTimers();
|
|
5
|
+
|
|
6
|
+
describe("useTypewriter", () => {
|
|
7
|
+
const sampleText = "Hello, world!";
|
|
8
|
+
const defaultOptions = {
|
|
9
|
+
text: sampleText,
|
|
10
|
+
typingSpeed: 50,
|
|
11
|
+
initialDelay: 500,
|
|
12
|
+
blinkingSpeed: 300,
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
it("should start with an empty displayedText", () => {
|
|
16
|
+
const { result } = renderHook(() => useTypewriter(defaultOptions));
|
|
17
|
+
expect(result.current.displayedText).toBe("");
|
|
18
|
+
expect(result.current.isComplete).toBe(false);
|
|
19
|
+
expect(result.current.showDot).toBe(true);
|
|
20
|
+
});
|
|
21
|
+
it("should handle empty text input gracefully", () => {
|
|
22
|
+
const { result } = renderHook(() =>
|
|
23
|
+
useTypewriter({ ...defaultOptions, text: "" })
|
|
24
|
+
);
|
|
25
|
+
|
|
26
|
+
// Assert initial state for empty text input
|
|
27
|
+
expect(result.current.displayedText).toBe(""); // Should be an empty string
|
|
28
|
+
expect(result.current.isComplete).toBe(true); // Typing is complete immediately
|
|
29
|
+
expect(result.current.showDot).toBe(false); // No blinking dot
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it("should display the correct text after typing is complete", async () => {
|
|
33
|
+
const text =
|
|
34
|
+
"Du beskriver dig själv som en person som värderar kroppstyper, engagemang, kompetens i livet.";
|
|
35
|
+
|
|
36
|
+
const { result } = renderHook(() =>
|
|
37
|
+
useTypewriter({
|
|
38
|
+
...defaultOptions,
|
|
39
|
+
text,
|
|
40
|
+
})
|
|
41
|
+
);
|
|
42
|
+
|
|
43
|
+
// Simulate the initial delay (before typing starts)
|
|
44
|
+
act(() => {
|
|
45
|
+
jest.advanceTimersByTime(defaultOptions.initialDelay);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
// Simulate the full typing duration (based on the text length and typing speed)
|
|
49
|
+
const totalTypingTime = text.length * defaultOptions.typingSpeed;
|
|
50
|
+
act(() => {
|
|
51
|
+
jest.advanceTimersByTime(totalTypingTime);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
// Assert that after typing is complete, the correct text is displayed
|
|
55
|
+
expect(result.current.displayedText).toBe(text);
|
|
56
|
+
expect(result.current.isComplete).toBe(true);
|
|
57
|
+
expect(result.current.showDot).toBe(false); // No more blinking dot
|
|
58
|
+
});
|
|
59
|
+
it("should respect typing speed and initial delay", () => {
|
|
60
|
+
const customOptions = {
|
|
61
|
+
...defaultOptions,
|
|
62
|
+
typingSpeed: 100,
|
|
63
|
+
initialDelay: 1000,
|
|
64
|
+
};
|
|
65
|
+
const { result } = renderHook(() => useTypewriter(customOptions));
|
|
66
|
+
|
|
67
|
+
// Before initial delay
|
|
68
|
+
act(() => {
|
|
69
|
+
jest.advanceTimersByTime(900);
|
|
70
|
+
});
|
|
71
|
+
expect(result.current.displayedText).toBe("");
|
|
72
|
+
expect(result.current.isComplete).toBe(false);
|
|
73
|
+
|
|
74
|
+
// After initial delay
|
|
75
|
+
act(() => {
|
|
76
|
+
jest.advanceTimersByTime(100);
|
|
77
|
+
});
|
|
78
|
+
expect(result.current.displayedText).toBe(sampleText.charAt(0));
|
|
79
|
+
|
|
80
|
+
// Verify typing speed
|
|
81
|
+
act(() => {
|
|
82
|
+
jest.advanceTimersByTime(customOptions.typingSpeed);
|
|
83
|
+
});
|
|
84
|
+
expect(result.current.displayedText).toBe(sampleText.slice(0, 2));
|
|
85
|
+
});
|
|
86
|
+
});
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { useEffect, useState } from "react";
|
|
2
|
+
|
|
3
|
+
interface UseTypewriterOptions {
|
|
4
|
+
text: string;
|
|
5
|
+
typingSpeed?: number; // Time in ms between each character
|
|
6
|
+
initialDelay?: number; // Time in ms before typing starts
|
|
7
|
+
blinkingSpeed?: number; // Time in ms for the blinking dot toggle
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export const useTypewriter = ({
|
|
11
|
+
text,
|
|
12
|
+
typingSpeed = 50,
|
|
13
|
+
initialDelay = 1000,
|
|
14
|
+
blinkingSpeed = 500,
|
|
15
|
+
}: UseTypewriterOptions) => {
|
|
16
|
+
const [isComplete, setIsComplete] = useState(false);
|
|
17
|
+
const [displayedText, setDisplayedText] = useState("");
|
|
18
|
+
const [showDot, setShowDot] = useState(false);
|
|
19
|
+
|
|
20
|
+
useEffect(() => {
|
|
21
|
+
if (!text || text.length === 0) {
|
|
22
|
+
setDisplayedText(""); // No text to display
|
|
23
|
+
setIsComplete(true); // Mark typing as complete immediately
|
|
24
|
+
setShowDot(false); // No blinking dot for empty text
|
|
25
|
+
return; // Exit early if text is empty
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
let blinkingInterval: NodeJS.Timeout | null = null;
|
|
29
|
+
let typingTimeout: NodeJS.Timeout | null = null;
|
|
30
|
+
|
|
31
|
+
// Initialize state
|
|
32
|
+
setDisplayedText(""); // Clear the displayed text
|
|
33
|
+
setShowDot(true); // Start with a blinking dot
|
|
34
|
+
setIsComplete(false); // Reset completion flag
|
|
35
|
+
|
|
36
|
+
// Start blinking dot effect
|
|
37
|
+
blinkingInterval = setInterval(() => {
|
|
38
|
+
setShowDot((prev) => !prev);
|
|
39
|
+
}, blinkingSpeed);
|
|
40
|
+
|
|
41
|
+
// Start typing after the initial delay
|
|
42
|
+
typingTimeout = setTimeout(() => {
|
|
43
|
+
if (blinkingInterval) {
|
|
44
|
+
clearInterval(blinkingInterval); // Stop blinking before typing
|
|
45
|
+
setShowDot(false); // Hide blinking dot
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Typing effect
|
|
49
|
+
let currentIndex = 0;
|
|
50
|
+
|
|
51
|
+
const typeNextChar = () => {
|
|
52
|
+
if (currentIndex < text.length) {
|
|
53
|
+
const charToAdd = text[currentIndex];
|
|
54
|
+
setDisplayedText((prev) => prev + charToAdd); // Append character
|
|
55
|
+
currentIndex++;
|
|
56
|
+
setTimeout(typeNextChar, typingSpeed); // Schedule next character
|
|
57
|
+
} else {
|
|
58
|
+
setIsComplete(true); // Mark as complete
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
// Trigger the first character manually
|
|
63
|
+
setDisplayedText(text.charAt(0));
|
|
64
|
+
currentIndex = 1;
|
|
65
|
+
|
|
66
|
+
// Schedule the rest of the characters
|
|
67
|
+
setTimeout(typeNextChar, typingSpeed);
|
|
68
|
+
}, initialDelay);
|
|
69
|
+
|
|
70
|
+
// Cleanup on unmount or dependencies change
|
|
71
|
+
return () => {
|
|
72
|
+
if (blinkingInterval) clearInterval(blinkingInterval);
|
|
73
|
+
if (typingTimeout) clearTimeout(typingTimeout);
|
|
74
|
+
};
|
|
75
|
+
}, [text, typingSpeed, initialDelay, blinkingSpeed]);
|
|
76
|
+
|
|
77
|
+
return { displayedText, showDot, isComplete };
|
|
78
|
+
};
|
package/src/index.ts
CHANGED
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
CreatePLSInputRequest,
|
|
11
11
|
CreatePLSSubmissionRequest,
|
|
12
12
|
CreateReportRequest,
|
|
13
|
+
GetCARequest,
|
|
13
14
|
GetMatchableUserRequest,
|
|
14
15
|
GetMatchCheckRequest,
|
|
15
16
|
GetMatchesRequest,
|
|
@@ -43,6 +44,7 @@ import {
|
|
|
43
44
|
CreatePLSInputResponse,
|
|
44
45
|
CreatePLSSubmissionResponse,
|
|
45
46
|
CreateReportResponse,
|
|
47
|
+
GetCAResponse,
|
|
46
48
|
GetMatchableUserResponse,
|
|
47
49
|
GetMatchableUsersResponse,
|
|
48
50
|
GetMatchCheckResponse,
|
|
@@ -87,6 +89,10 @@ export type CloudFunctionTypes = {
|
|
|
87
89
|
};
|
|
88
90
|
|
|
89
91
|
// ca
|
|
92
|
+
[CloudFunction.GET_CA]: {
|
|
93
|
+
payload: GetCARequest;
|
|
94
|
+
response: GetCAResponse;
|
|
95
|
+
};
|
|
90
96
|
[CloudFunction.SUBMIT_ANSWER]: {
|
|
91
97
|
payload: SubmitAnswerRequest;
|
|
92
98
|
response: SubmitAnswerResponse;
|