@gateweb/react-utils 2.3.0 → 2.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,6 +1,8 @@
1
1
  # @gateweb/react-utils
2
2
 
3
- **`@gateweb/react-utils`** is a comprehensive, TypeScript-first utility library designed to streamline development in React projects at GateWeb. It provides a collection of robust, well-tested, and reusable functions and hooks, covering everything from date manipulation and type conversion to custom hooks and web APIs.
3
+ **`@gateweb/react-utils`** GateWeb TypeScript-first utility library,包含常用的 functions / hooks / types(日期、格式化、轉換、驗證、web、React hooks 等),並且有測試與自動化文件。
4
+
5
+ > This repo also provides a docs site + auto-generated API reference.
4
6
 
5
7
  [![npm version](https://img.shields.io/npm/v/@gateweb/react-utils.svg)](https://www.npmjs.com/package/@gateweb/react-utils)
6
8
 
@@ -27,95 +29,15 @@ npm install @gateweb/react-utils
27
29
  yarn add @gateweb/react-utils
28
30
  ```
29
31
 
30
- ## 🚀 Usage
31
-
32
- Here are some examples of the most common utilities.
33
-
34
- ### React Hooks
35
-
36
- #### `useCountdown`
37
-
38
- A hook to manage a countdown timer.
39
-
40
- ```tsx
41
- import { useCountdown } from '@gateweb/react-utils';
42
-
43
- function MyComponent() {
44
- const { count, start, stop, reset } = useCountdown({ initialValue: 60 });
45
-
46
- return (
47
- <div>
48
- <p>Countdown: {count}</p>
49
- <button onClick={start}>Start</button>
50
- <button onClick={stop}>Stop</button>
51
- <button onClick={reset}>Reset</button>
52
- </div>
53
- );
54
- }
55
- ```
56
-
57
- ### Date Utilities
58
-
59
- #### `formatDate`
60
-
61
- Format a date object or string into a custom format.
62
-
63
- ```ts
64
- import { formatDate } from '@gateweb/react-utils';
65
-
66
- const formattedDate = formatDate(new Date(), 'YYYY-MM-DD');
67
- console.log(formattedDate); // e.g., "2023-10-27"
68
- ```
69
-
70
- #### `getPeriod`
71
-
72
- Get a predefined date period, like "last 7 days".
73
-
74
- ```ts
75
- import { getPeriod } from '@gateweb/react-utils';
76
-
77
- const last7Days = getPeriod('last_7_days');
78
- console.log(last7Days); // { startDate: Dayjs, endDate: Dayjs }
79
- ```
80
-
81
- ### Core Utilities
82
-
83
- #### `toCamelCase`
84
-
85
- Convert a string from snake_case or kebab-case to camelCase.
86
-
87
- ```ts
88
- import { toCamelCase } from '@gateweb/react-utils';
89
-
90
- const camel = toCamelCase('hello_world-example');
91
- console.log(camel); // "helloWorldExample"
92
- ```
93
-
94
- #### `bytesToSize`
95
-
96
- Convert bytes to a human-readable format (KB, MB, GB).
32
+ ## 🚀 快速開始(Quick Start)
97
33
 
98
34
  ```ts
99
35
  import { bytesToSize } from '@gateweb/react-utils';
100
36
 
101
- const size = bytesToSize(1024 * 1024 * 5); // 5MB
102
- console.log(size); // "5 MB"
37
+ bytesToSize(1024);
103
38
  ```
104
39
 
105
- ### Web APIs
106
-
107
- #### `download`
108
-
109
- A simple utility to trigger a file download in the browser.
110
-
111
- ```ts
112
- import { download } from '@gateweb/react-utils';
113
-
114
- function handleDownload() {
115
- const blob = new Blob(['Hello, world!'], { type: 'text/plain' });
116
- download(blob, 'hello.txt');
117
- }
118
- ```
40
+ 更多範例與用法請看 [react-utils docs](https://crispy-dollop-o765k9n.pages.github.io/)
119
41
 
120
42
  ## 🤝 Contributing
121
43
 
@@ -286,6 +286,35 @@ declare function mergeRefs<T = any>(refs: Array<React__default.MutableRefObject<
286
286
  * downloadFile(new Blob(['test content'], { type: 'text/plain' }), 'testfile', 'txt');
287
287
  */
288
288
  declare const downloadFile: (source: string | Blob, filename?: string, fileExtension?: string) => void;
289
+ type DownloadFileOptions = {
290
+ /**
291
+ * The url string to download.
292
+ */
293
+ url: string;
294
+ /**
295
+ * The filename to use for the downloaded file (without extension)
296
+ *
297
+ * @default The current timestamp as a string (e.g. "1686789123456")
298
+ */
299
+ filename?: string;
300
+ /**
301
+ * The file extension to append to the filename (e.g. "txt", "pdf").
302
+ */
303
+ fileExtension?: string;
304
+ /**
305
+ * `fetch` init options (e.g. `credentials`, `headers`).
306
+ *
307
+ * Note: the URL must allow CORS (or be same-origin) for the browser to read the response.
308
+ */
309
+ fetchInit?: RequestInit;
310
+ };
311
+ /**
312
+ * Downloads a file by fetching the URL into a Blob first, then triggering a blob download.
313
+ *
314
+ * This is useful when a URL returns a previewable Content-Type (e.g. `application/pdf`) and
315
+ * the browser would otherwise open a new tab to preview instead of downloading.
316
+ */
317
+ declare const downloadFileWithFetch: ({ url, filename, fileExtension, fetchInit, }: DownloadFileOptions) => Promise<void>;
289
318
 
290
319
  /**
291
320
  * 從 localStorage 取得資料,支援槽狀取值(Json 物件)
@@ -307,5 +336,5 @@ declare const getLocalStorage: <T>(key: string, deCode?: boolean) => T | undefin
307
336
  */
308
337
  declare const setLocalStorage: (key: string, value: Record<string, any>, enCode?: boolean) => void;
309
338
 
310
- export { QueryProvider, createDataContext, createStoreContext, downloadFile, getLocalStorage, mergeRefs, setLocalStorage, useCountdown, useDisclosure, useHorizontalWheel, useQueryContext, useValue };
311
- export type { TCountdownActions, UseDisclosureReturn };
339
+ export { QueryProvider, createDataContext, createStoreContext, downloadFile, downloadFileWithFetch, getLocalStorage, mergeRefs, setLocalStorage, useCountdown, useDisclosure, useHorizontalWheel, useQueryContext, useValue };
340
+ export type { DownloadFileOptions, TCountdownActions, UseDisclosureReturn };
@@ -6,7 +6,7 @@ var React = require('react');
6
6
  var useCountdown12s = require('./useCountdown-12s-uiqhgllY.js');
7
7
  var useDisclosure12s = require('./useDisclosure-12s-SZtbSE4A.js');
8
8
  var useHorizontalWheel12s = require('./useHorizontalWheel-12s-bwbWVJeM.js');
9
- var download12s = require('./download-12s-DKxkL92w.js');
9
+ var download12s = require('./download-12s-C5yIKqRX.js');
10
10
  var webStorage12s = require('./webStorage-12s-0RtNO_uc.js');
11
11
 
12
12
  function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
@@ -114,6 +114,7 @@ exports.useCountdown = useCountdown12s.useCountdown;
114
114
  exports.useDisclosure = useDisclosure12s.useDisclosure;
115
115
  exports.useHorizontalWheel = useHorizontalWheel12s.useHorizontalWheel;
116
116
  exports.downloadFile = download12s.downloadFile;
117
+ exports.downloadFileWithFetch = download12s.downloadFileWithFetch;
117
118
  exports.getLocalStorage = webStorage12s.getLocalStorage;
118
119
  exports.setLocalStorage = webStorage12s.setLocalStorage;
119
120
  exports.createDataContext = createDataContext;
@@ -0,0 +1,65 @@
1
+ 'use client';
2
+ const sanitizeUrlForError = (input)=>{
3
+ try {
4
+ const parsed = new URL(input);
5
+ return `${parsed.origin}${parsed.pathname}`;
6
+ } catch {
7
+ return input.split('#')[0].split('?')[0];
8
+ }
9
+ };
10
+ /**
11
+ * Downloads a file from a given source.
12
+ *
13
+ * @param source - The source of the file to be downloaded. It can be a URL string or a Blob object.
14
+ * @param filename - The name of the file to be downloaded. Defaults to the current timestamp if not provided.
15
+ * @param fileExtension - The file extension to be appended to the filename. Optional.
16
+ *
17
+ * @example
18
+ * downloadFile('http://example.com/file.txt', 'testfile', 'txt');
19
+ * downloadFile(new Blob(['test content'], { type: 'text/plain' }), 'testfile', 'txt');
20
+ */ const downloadFile = (source, filename = new Date().getTime().toString(), fileExtension)=>{
21
+ const shouldRevokeObjectUrl = typeof source !== 'string';
22
+ const url = shouldRevokeObjectUrl ? URL.createObjectURL(source) : source;
23
+ const link = document.createElement('a');
24
+ link.id = `download-${new Date().getTime()}`;
25
+ document.body.appendChild(link);
26
+ if (!fileExtension) {
27
+ link.download = filename;
28
+ } else {
29
+ link.download = `${filename}.${fileExtension}`;
30
+ }
31
+ link.href = url;
32
+ link.target = '_blank';
33
+ link.click();
34
+ link.remove();
35
+ if (shouldRevokeObjectUrl) {
36
+ // Defer revocation to avoid interfering with the download in some browsers.
37
+ setTimeout(()=>{
38
+ URL.revokeObjectURL(url);
39
+ }, 0);
40
+ }
41
+ };
42
+ /**
43
+ * Downloads a file by fetching the URL into a Blob first, then triggering a blob download.
44
+ *
45
+ * This is useful when a URL returns a previewable Content-Type (e.g. `application/pdf`) and
46
+ * the browser would otherwise open a new tab to preview instead of downloading.
47
+ */ const downloadFileWithFetch = async ({ url, filename = new Date().getTime().toString(), fileExtension, fetchInit })=>{
48
+ const safeUrl = sanitizeUrlForError(url);
49
+ let response;
50
+ try {
51
+ response = await fetch(url, fetchInit);
52
+ } catch (error) {
53
+ throw new Error(`downloadFileWithFetch: fetch failed for ${safeUrl}`, {
54
+ cause: error
55
+ });
56
+ }
57
+ if (!response.ok) {
58
+ throw new Error(`downloadFileWithFetch: failed to fetch ${safeUrl} (status ${response.status} ${response.statusText})`);
59
+ }
60
+ const blob = await response.blob();
61
+ downloadFile(blob, filename, fileExtension);
62
+ };
63
+
64
+ exports.downloadFile = downloadFile;
65
+ exports.downloadFileWithFetch = downloadFileWithFetch;
@@ -286,6 +286,35 @@ declare function mergeRefs<T = any>(refs: Array<React__default.MutableRefObject<
286
286
  * downloadFile(new Blob(['test content'], { type: 'text/plain' }), 'testfile', 'txt');
287
287
  */
288
288
  declare const downloadFile: (source: string | Blob, filename?: string, fileExtension?: string) => void;
289
+ type DownloadFileOptions = {
290
+ /**
291
+ * The url string to download.
292
+ */
293
+ url: string;
294
+ /**
295
+ * The filename to use for the downloaded file (without extension)
296
+ *
297
+ * @default The current timestamp as a string (e.g. "1686789123456")
298
+ */
299
+ filename?: string;
300
+ /**
301
+ * The file extension to append to the filename (e.g. "txt", "pdf").
302
+ */
303
+ fileExtension?: string;
304
+ /**
305
+ * `fetch` init options (e.g. `credentials`, `headers`).
306
+ *
307
+ * Note: the URL must allow CORS (or be same-origin) for the browser to read the response.
308
+ */
309
+ fetchInit?: RequestInit;
310
+ };
311
+ /**
312
+ * Downloads a file by fetching the URL into a Blob first, then triggering a blob download.
313
+ *
314
+ * This is useful when a URL returns a previewable Content-Type (e.g. `application/pdf`) and
315
+ * the browser would otherwise open a new tab to preview instead of downloading.
316
+ */
317
+ declare const downloadFileWithFetch: ({ url, filename, fileExtension, fetchInit, }: DownloadFileOptions) => Promise<void>;
289
318
 
290
319
  /**
291
320
  * 從 localStorage 取得資料,支援槽狀取值(Json 物件)
@@ -307,5 +336,5 @@ declare const getLocalStorage: <T>(key: string, deCode?: boolean) => T | undefin
307
336
  */
308
337
  declare const setLocalStorage: (key: string, value: Record<string, any>, enCode?: boolean) => void;
309
338
 
310
- export { QueryProvider, createDataContext, createStoreContext, downloadFile, getLocalStorage, mergeRefs, setLocalStorage, useCountdown, useDisclosure, useHorizontalWheel, useQueryContext, useValue };
311
- export type { TCountdownActions, UseDisclosureReturn };
339
+ export { QueryProvider, createDataContext, createStoreContext, downloadFile, downloadFileWithFetch, getLocalStorage, mergeRefs, setLocalStorage, useCountdown, useDisclosure, useHorizontalWheel, useQueryContext, useValue };
340
+ export type { DownloadFileOptions, TCountdownActions, UseDisclosureReturn };
@@ -4,7 +4,7 @@ import React, { useMemo, createContext, useContext, useState, useCallback } from
4
4
  export { u as useCountdown } from './useCountdown-12s-t52WIHfq.mjs';
5
5
  export { u as useDisclosure } from './useDisclosure-12s-BQAHpAXK.mjs';
6
6
  export { u as useHorizontalWheel } from './useHorizontalWheel-12s-D3IfutV9.mjs';
7
- export { d as downloadFile } from './download-12s-CnaJ0p_f.mjs';
7
+ export { d as downloadFile, a as downloadFileWithFetch } from './download-12s-DtxLfLpr.mjs';
8
8
  export { g as getLocalStorage, s as setLocalStorage } from './webStorage-12s-Bo7x8q5t.mjs';
9
9
 
10
10
  /**
@@ -0,0 +1,64 @@
1
+ 'use client';
2
+ const sanitizeUrlForError = (input)=>{
3
+ try {
4
+ const parsed = new URL(input);
5
+ return `${parsed.origin}${parsed.pathname}`;
6
+ } catch {
7
+ return input.split('#')[0].split('?')[0];
8
+ }
9
+ };
10
+ /**
11
+ * Downloads a file from a given source.
12
+ *
13
+ * @param source - The source of the file to be downloaded. It can be a URL string or a Blob object.
14
+ * @param filename - The name of the file to be downloaded. Defaults to the current timestamp if not provided.
15
+ * @param fileExtension - The file extension to be appended to the filename. Optional.
16
+ *
17
+ * @example
18
+ * downloadFile('http://example.com/file.txt', 'testfile', 'txt');
19
+ * downloadFile(new Blob(['test content'], { type: 'text/plain' }), 'testfile', 'txt');
20
+ */ const downloadFile = (source, filename = new Date().getTime().toString(), fileExtension)=>{
21
+ const shouldRevokeObjectUrl = typeof source !== 'string';
22
+ const url = shouldRevokeObjectUrl ? URL.createObjectURL(source) : source;
23
+ const link = document.createElement('a');
24
+ link.id = `download-${new Date().getTime()}`;
25
+ document.body.appendChild(link);
26
+ if (!fileExtension) {
27
+ link.download = filename;
28
+ } else {
29
+ link.download = `${filename}.${fileExtension}`;
30
+ }
31
+ link.href = url;
32
+ link.target = '_blank';
33
+ link.click();
34
+ link.remove();
35
+ if (shouldRevokeObjectUrl) {
36
+ // Defer revocation to avoid interfering with the download in some browsers.
37
+ setTimeout(()=>{
38
+ URL.revokeObjectURL(url);
39
+ }, 0);
40
+ }
41
+ };
42
+ /**
43
+ * Downloads a file by fetching the URL into a Blob first, then triggering a blob download.
44
+ *
45
+ * This is useful when a URL returns a previewable Content-Type (e.g. `application/pdf`) and
46
+ * the browser would otherwise open a new tab to preview instead of downloading.
47
+ */ const downloadFileWithFetch = async ({ url, filename = new Date().getTime().toString(), fileExtension, fetchInit })=>{
48
+ const safeUrl = sanitizeUrlForError(url);
49
+ let response;
50
+ try {
51
+ response = await fetch(url, fetchInit);
52
+ } catch (error) {
53
+ throw new Error(`downloadFileWithFetch: fetch failed for ${safeUrl}`, {
54
+ cause: error
55
+ });
56
+ }
57
+ if (!response.ok) {
58
+ throw new Error(`downloadFileWithFetch: failed to fetch ${safeUrl} (status ${response.status} ${response.statusText})`);
59
+ }
60
+ const blob = await response.blob();
61
+ downloadFile(blob, filename, fileExtension);
62
+ };
63
+
64
+ export { downloadFileWithFetch as a, downloadFile as d };
package/package.json CHANGED
@@ -1,8 +1,12 @@
1
1
  {
2
2
  "name": "@gateweb/react-utils",
3
- "version": "2.3.0",
3
+ "version": "2.5.0",
4
4
  "description": "React Utils for GateWeb",
5
- "homepage": "https://github.com/GatewebSolutions/react-utils",
5
+ "homepage": "https://github.com/GW-VAT-BPSP-Team/react-utils",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "git+https://github.com/GW-VAT-BPSP-Team/react-utils.git"
9
+ },
6
10
  "files": [
7
11
  "dist"
8
12
  ],
@@ -69,9 +73,12 @@
69
73
  "jest-environment-jsdom": "^30.0.5",
70
74
  "lint-staged": "^16.1.2",
71
75
  "prettier": "^3.6.2",
76
+ "typedoc": "^0.28.0",
77
+ "typedoc-plugin-markdown": "^4.8.0",
72
78
  "ts-jest": "^29.4.0",
73
79
  "ts-node": "^10.9.2",
74
80
  "typescript": "^5.8.3",
81
+ "vitepress": "^1.6.3",
75
82
  "vitest": "^3.2.4"
76
83
  },
77
84
  "commitlint": {
@@ -86,6 +93,13 @@
86
93
  "test:coverage": "vitest run --coverage",
87
94
  "test:ui": "vitest --ui --coverage.enabled=true",
88
95
  "build": "bunchee",
89
- "build:prepare": "bunchee --prepare"
96
+ "build:prepare": "bunchee --prepare",
97
+ "api-docs:main": "typedoc --options typedoc.base.json --out docs/api/main src/index.ts && node scripts/generate-api-file-pages.js main",
98
+ "api-docs:client": "typedoc --options typedoc.base.json --out docs/api/client src/client.ts && node scripts/generate-api-file-pages.js client",
99
+ "api-docs:types": "typedoc --options typedoc.base.json --out docs/api/types src/types/index.ts && node scripts/generate-api-file-pages.js types",
100
+ "api-docs:build": "pnpm api-docs:main && pnpm api-docs:client && pnpm api-docs:types",
101
+ "docs:dev": "pnpm api-docs:build && vitepress dev docs",
102
+ "docs:build": "pnpm api-docs:build && vitepress build docs",
103
+ "docs:preview": "vitepress preview docs"
90
104
  }
91
105
  }
@@ -1,27 +0,0 @@
1
- 'use client';
2
- /**
3
- * Downloads a file from a given source.
4
- *
5
- * @param source - The source of the file to be downloaded. It can be a URL string or a Blob object.
6
- * @param filename - The name of the file to be downloaded. Defaults to the current timestamp if not provided.
7
- * @param fileExtension - The file extension to be appended to the filename. Optional.
8
- *
9
- * @example
10
- * downloadFile('http://example.com/file.txt', 'testfile', 'txt');
11
- * downloadFile(new Blob(['test content'], { type: 'text/plain' }), 'testfile', 'txt');
12
- */ const downloadFile = (source, filename = new Date().getTime().toString(), fileExtension)=>{
13
- const url = typeof source === 'string' ? source : URL.createObjectURL(source);
14
- const link = document.createElement('a');
15
- link.id = `download-${new Date().getTime()}`;
16
- document.body.appendChild(link);
17
- if (!fileExtension) {
18
- link.download = filename;
19
- } else {
20
- link.download = `${filename}.${fileExtension}`;
21
- }
22
- link.href = url;
23
- link.target = '_blank';
24
- link.click();
25
- };
26
-
27
- exports.downloadFile = downloadFile;
@@ -1,27 +0,0 @@
1
- 'use client';
2
- /**
3
- * Downloads a file from a given source.
4
- *
5
- * @param source - The source of the file to be downloaded. It can be a URL string or a Blob object.
6
- * @param filename - The name of the file to be downloaded. Defaults to the current timestamp if not provided.
7
- * @param fileExtension - The file extension to be appended to the filename. Optional.
8
- *
9
- * @example
10
- * downloadFile('http://example.com/file.txt', 'testfile', 'txt');
11
- * downloadFile(new Blob(['test content'], { type: 'text/plain' }), 'testfile', 'txt');
12
- */ const downloadFile = (source, filename = new Date().getTime().toString(), fileExtension)=>{
13
- const url = typeof source === 'string' ? source : URL.createObjectURL(source);
14
- const link = document.createElement('a');
15
- link.id = `download-${new Date().getTime()}`;
16
- document.body.appendChild(link);
17
- if (!fileExtension) {
18
- link.download = filename;
19
- } else {
20
- link.download = `${filename}.${fileExtension}`;
21
- }
22
- link.href = url;
23
- link.target = '_blank';
24
- link.click();
25
- };
26
-
27
- export { downloadFile as d };