Skip to content

Commit 81d6173

Browse files
committed
main 🧊 add demos and tests
1 parent 7d29620 commit 81d6173

46 files changed

Lines changed: 3201 additions & 162 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

packages/core/src/bundle/hooks/useFileSystemAccess/useFileSystemAccess.js

Lines changed: 14 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -30,54 +30,52 @@ export const useFileSystemAccess = (options = {}) => {
3030
const [file, setFile] = useState();
3131
const load = async () => {
3232
const handle = handleRef.current;
33-
if (!handle) return;
33+
if (!handle) throw new Error('No file handle');
3434
const file = await handle.getFile();
3535
setFile(file);
36-
if (dataType === 'Text') return setData(await file.text());
37-
if (dataType === 'ArrayBuffer') return setData(await file.arrayBuffer());
38-
if (dataType === 'Blob') return setData(file);
39-
throw new Error(`Invalid data type: ${dataType}`);
36+
const actionMap = {
37+
Text: () => file.text(),
38+
ArrayBuffer: () => file.arrayBuffer(),
39+
Blob: () => file
40+
};
41+
const data = await actionMap[dataType]();
42+
setData(data);
43+
return data;
4044
};
4145
const open = async (params) => {
42-
if (!supported) return;
4346
const [handle] = await window.showOpenFilePicker({
4447
...options,
4548
...params
4649
});
4750
handleRef.current = handle;
48-
await load();
51+
return load();
4952
};
5053
const create = async (params = {}) => {
51-
if (!supported) return;
5254
handleRef.current = await window.showSaveFilePicker({
5355
...options,
5456
...params
5557
});
5658
setData(undefined);
57-
await load();
59+
return load();
5860
};
5961
const saveAs = async (params) => {
60-
if (!supported) return;
6162
handleRef.current = await window.showSaveFilePicker({
6263
...options,
6364
...params
6465
});
6566
const writable = await handleRef.current.createWritable();
6667
await writable.write(data);
6768
await writable.close();
68-
await load();
69+
return load();
6970
};
7071
const save = async (params) => {
71-
if (!supported) return;
7272
if (!handleRef.current) return saveAs(params);
7373
const writable = await handleRef.current.createWritable();
7474
await writable.write(data);
7575
await writable.close();
76-
await load();
77-
};
78-
const update = async () => {
79-
await load();
76+
return load();
8077
};
78+
const update = load;
8179
const set = (data) => setData(data);
8280
return {
8381
supported,

packages/core/src/bundle/hooks/useFocusTrap/useFocusTrap.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ export const useFocusTrap = (...params) => {
6060
if (event.shiftKey && document.activeElement === firstElement) {
6161
event.preventDefault();
6262
lastElement.focus();
63+
return;
6364
}
6465
if (document.activeElement === lastElement) {
6566
event.preventDefault();

packages/core/src/bundle/hooks/useFps/useFps.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import { useEffect, useState } from 'react';
1212
* @example
1313
* const fps = useFps();
1414
*/
15-
export const useFps = () => {
15+
export const useFps = (callback) => {
1616
const [fps, setFps] = useState(0);
1717
useEffect(() => {
1818
let frameCount = 0;
@@ -25,6 +25,7 @@ export const useFps = () => {
2525
if (elapsedTime >= 1000) {
2626
const calculatedFps = Math.round((frameCount * 1000) / elapsedTime);
2727
setFps(calculatedFps);
28+
callback?.(calculatedFps);
2829
frameCount = 0;
2930
startTime = currentTime;
3031
}

packages/core/src/hooks/useDocumentEvent/useDocumentEvent.demo.tsx

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,12 @@ const Demo = () => {
77
const [popId, setPopId] = useState(0);
88

99
useDocumentEvent('click', (event) => {
10+
const target = event.target;
11+
12+
if (!(target instanceof Element) || !target.closest('[data-cookie-target]')) {
13+
return;
14+
}
15+
1016
cookies.inc();
1117

1218
const id = popId;
@@ -20,7 +26,12 @@ const Demo = () => {
2026

2127
return (
2228
<section className='flex flex-col items-center gap-4 p-8 select-none'>
23-
<span className='text-7xl transition-transform duration-100 active:scale-95'>🍪</span>
29+
<span
30+
data-cookie-target
31+
className='cursor-pointer text-7xl transition-transform duration-100 active:scale-95'
32+
>
33+
🍪
34+
</span>
2435

2536
<div className='flex flex-col items-center gap-1'>
2637
<span className='text-foreground font-mono text-5xl font-semibold tabular-nums'>

packages/core/src/hooks/useFileSystemAccess/useFileSystemAccess.demo.tsx

Lines changed: 152 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,13 @@
1-
import { useFileSystemAccess } from '@siberiacancode/reactuse';
1+
import type { MouseEvent } from 'react';
2+
3+
import {
4+
useBoolean,
5+
useClickOutside,
6+
useField,
7+
useFileSystemAccess
8+
} from '@siberiacancode/reactuse';
9+
import { FileTextIcon, ReplaceIcon, XIcon } from 'lucide-react';
10+
import { useState } from 'react';
211

312
const Demo = () => {
413
const fileSystemAccess = useFileSystemAccess({
@@ -11,60 +20,163 @@ const Demo = () => {
1120
]
1221
});
1322

23+
const findField = useField('');
24+
const replaceField = useField('');
25+
const [findOpen, toggleFindOpen] = useBoolean(false);
26+
const [content, setContent] = useState('');
27+
28+
const findPanelRef = useClickOutside<HTMLDivElement>(() => {
29+
toggleFindOpen(false);
30+
findField.reset();
31+
replaceField.reset();
32+
});
33+
1434
if (!fileSystemAccess.supported)
1535
return (
1636
<p>
17-
Api not supported, make sure to check for compatibility with different browsers when using
37+
API not supported, make sure to check for compatibility with different browsers when using
1838
this{' '}
1939
<a
2040
href='https://developer.mozilla.org/en-US/docs/Web/API/File_System_Access_API'
2141
rel='noreferrer'
2242
target='_blank'
2343
>
24-
api
44+
API
2545
</a>
2646
</p>
2747
);
2848

29-
return (
30-
<div className='flex max-w-lg flex-col gap-3'>
31-
<textarea
32-
className='box-border min-h-[12rem] w-full resize-y rounded-lg border border-zinc-200 bg-[var(--vp-c-bg-soft)] p-2 font-mono text-sm text-zinc-900 dark:border-zinc-600 dark:text-zinc-100'
33-
placeholder='Enter your text here'
34-
rows={4}
35-
value={fileSystemAccess.data ?? ''}
36-
onChange={(event) => fileSystemAccess.set(event.target.value)}
37-
/>
38-
39-
<div className='flex flex-wrap gap-2'>
40-
<button type='button' onClick={() => void fileSystemAccess.open()}>
41-
Open
42-
</button>
43-
<button type='button' onClick={() => void fileSystemAccess.save()}>
44-
Save
45-
</button>
46-
<button type='button' onClick={() => void fileSystemAccess.update()}>
47-
Reload
48-
</button>
49-
</div>
49+
const find = findField.watch();
50+
const replace = replaceField.watch();
51+
const matches = (fileSystemAccess.data ?? '').split(find).length - 1 || 0;
52+
const dirty = !!fileSystemAccess.file && content !== fileSystemAccess.data;
5053

51-
{fileSystemAccess.file && (
52-
<div className='flex flex-col gap-2'>
53-
<div>
54-
name: <code>{fileSystemAccess.name}</code>
55-
</div>
56-
<div>
57-
type: <code>{fileSystemAccess.type}</code>
58-
</div>
59-
<div>
60-
size: <code>{fileSystemAccess.size}</code>
61-
</div>
62-
<div>
63-
lastModified: <code>{fileSystemAccess.lastModified}</code>
54+
const onSave = async (event: MouseEvent<HTMLButtonElement>) => {
55+
event.preventDefault();
56+
await fileSystemAccess.save();
57+
setContent(fileSystemAccess.data ?? '');
58+
};
59+
60+
const onOpen = async (event: MouseEvent<HTMLButtonElement>) => {
61+
event.preventDefault();
62+
const data = await fileSystemAccess.open();
63+
setContent(data);
64+
};
65+
66+
const onReplaceAll = (event: MouseEvent<HTMLButtonElement>) => {
67+
event.preventDefault();
68+
if (!find || !matches) return;
69+
fileSystemAccess.set((fileSystemAccess.data ?? '').split(find).join(replace));
70+
findField.setValue('');
71+
replaceField.setValue('');
72+
};
73+
74+
return (
75+
<section className='demo-ui flex w-full max-w-2xl flex-col p-4'>
76+
<div className='border-border bg-card relative flex h-[280px] flex-col overflow-hidden rounded-xl border shadow-sm'>
77+
{!fileSystemAccess.file && (
78+
<div className='flex size-full flex-col items-center justify-center gap-3 p-6'>
79+
<div className='bg-muted text-muted-foreground flex size-12 items-center justify-center rounded-full'>
80+
<FileTextIcon className='size-6' />
81+
</div>
82+
<div className='flex flex-col items-center gap-1 text-center'>
83+
<p className='text-foreground text-sm font-medium'>No file opened</p>
84+
<p className='text-muted-foreground text-xs'>Open a .txt file to start editing</p>
85+
</div>
86+
<button data-size='sm' type='button' onClick={onOpen}>
87+
Open file
88+
</button>
6489
</div>
65-
</div>
66-
)}
67-
</div>
90+
)}
91+
92+
{fileSystemAccess.file && (
93+
<>
94+
<div className='border-border bg-muted/40 flex shrink-0 items-center gap-2 border-b px-3 py-2'>
95+
<div className='bg-card flex size-6 shrink-0 items-center justify-center'>
96+
<FileTextIcon className='text-muted-foreground size-3' />
97+
</div>
98+
<span className='text-foreground min-w-0 flex-1 truncate text-xs font-medium'>
99+
{fileSystemAccess.name}
100+
</span>
101+
102+
<div className='flex items-center gap-1'>
103+
<button
104+
aria-label='Find and replace'
105+
data-size='icon-sm'
106+
data-variant='ghost'
107+
type='button'
108+
onClick={() => toggleFindOpen()}
109+
>
110+
<ReplaceIcon className='size-3.5' />
111+
</button>
112+
<button data-size='sm' disabled={!dirty} type='button' onClick={onSave}>
113+
Save
114+
</button>
115+
</div>
116+
</div>
117+
118+
<textarea
119+
className='no-scrollbar text-foreground flex-1 resize-none rounded-none! border-none! bg-transparent p-3 font-mono text-xs shadow-none! ring-0! outline-none!'
120+
value={fileSystemAccess.data}
121+
onChange={(event) => fileSystemAccess.set(event.target.value)}
122+
/>
123+
124+
{findOpen && (
125+
<div
126+
ref={findPanelRef}
127+
className='border-border bg-card absolute top-12 right-3 z-20 flex w-[240px] flex-col gap-3 rounded-xl border p-3 shadow-lg'
128+
>
129+
<div className='flex items-center justify-between'>
130+
<span className='text-foreground text-[11px] font-medium'>Find and replace</span>
131+
<button
132+
aria-label='Close'
133+
data-size='icon-xs'
134+
data-variant='ghost'
135+
type='button'
136+
onClick={() => toggleFindOpen()}
137+
>
138+
<XIcon className='size-3' />
139+
</button>
140+
</div>
141+
142+
<div className='flex flex-col gap-2'>
143+
<input
144+
autoFocus
145+
className='border-border bg-background text-foreground rounded-md border px-2.5 py-1.5 text-[11px] outline-none'
146+
placeholder='Find'
147+
{...findField.register()}
148+
/>
149+
<div className='relative'>
150+
<input
151+
className='border-border bg-background text-foreground w-full rounded-md border px-2.5 py-1.5 pr-8 text-[11px] outline-none'
152+
placeholder='Replace with'
153+
{...replaceField.register()}
154+
/>
155+
<button
156+
aria-label='Replace all'
157+
className='absolute top-1/2 right-1 -translate-y-1/2'
158+
data-size='icon-xs'
159+
data-variant='ghost'
160+
disabled={!find || !matches}
161+
type='button'
162+
onClick={onReplaceAll}
163+
>
164+
<ReplaceIcon className='size-3' />
165+
</button>
166+
</div>
167+
</div>
168+
169+
{!!find && (
170+
<span className='text-muted-foreground font-mono text-[10px] tabular-nums'>
171+
{matches} {matches === 1 ? 'match' : 'matches'}
172+
</span>
173+
)}
174+
</div>
175+
)}
176+
</>
177+
)}
178+
</div>
179+
</section>
68180
);
69181
};
70182

0 commit comments

Comments
 (0)