Skip to content

Commit 7fb4b32

Browse files
committed
main 🧊 add new demos, rework meta for functions
1 parent 66a00de commit 7fb4b32

76 files changed

Lines changed: 1567 additions & 1055 deletions

File tree

Some content is hidden

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

‎.prettierignore‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,4 @@
22
**/node_modules
33
**/cache
44
**/pnpm-lock.yaml
5-
**/.next
5+
**/.next

‎packages/core/src/hooks/useAsync/useAsync.demo.tsx‎

Lines changed: 75 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,47 +1,93 @@
11
import { useAsync, useCounter } from '@siberiacancode/reactuse';
2+
import { ArrowLeftIcon, ArrowRightIcon, Loader2Icon } from 'lucide-react';
23

34
interface Pokemon {
5+
base_experience: number;
6+
height: number;
47
id: number;
58
name: string;
9+
weight: number;
610
}
711

8-
const getPokemon = (id: number) =>
9-
fetch(`https://pokeapi.co/api/v2/pokemon/${id}`).then((res) => res.json()) as Promise<Pokemon>;
12+
const getPokemon = async (id: number) => {
13+
await new Promise((resolve) => setTimeout(resolve, 1000));
14+
if (id === 3) throw new Error('Pokemon blocked for demo');
15+
return fetch(`https://pokeapi.co/api/v2/pokemon/${id}`).then((res) =>
16+
res.json()
17+
) as Promise<Pokemon>;
18+
};
1019

1120
const Demo = () => {
1221
const counter = useCounter(1);
1322
const getPokemonQuery = useAsync(() => getPokemon(counter.value), [counter.value]);
1423

1524
return (
16-
<>
17-
<button disabled={counter.value === 1} type='button' onClick={() => counter.dec()}>
18-
Prev
19-
</button>
20-
<button type='button' onClick={() => counter.inc()}>
21-
Next
22-
</button>
23-
24-
{getPokemonQuery.isLoading && (
25-
<div className='flex animate-pulse flex-col gap-2'>
26-
<div className='h-7 w-40 rounded-md bg-neutral-600' />
27-
<div className='size-96 rounded-md bg-neutral-600' />
25+
<section className='flex flex-col gap-4'>
26+
<div className='flex items-center justify-between gap-4'>
27+
<p>
28+
Index: <code>{counter.value}</code>
29+
</p>
30+
31+
<div className='flex gap-2'>
32+
<button
33+
disabled={counter.value === 1 || getPokemonQuery.isLoading}
34+
type='button'
35+
onClick={() => counter.dec()}
36+
>
37+
<ArrowLeftIcon className='size-4' /> Prev
38+
</button>
39+
40+
<button disabled={getPokemonQuery.isLoading} type='button' onClick={() => counter.inc()}>
41+
Next <ArrowRightIcon className='size-4' />
42+
</button>
2843
</div>
29-
)}
30-
31-
{getPokemonQuery.data && !getPokemonQuery.isLoading && (
32-
<div className='flex flex-col gap-2'>
33-
<p>
34-
Name: <code>{getPokemonQuery.data.name}</code>
35-
</p>
36-
<div className='size-96'>
37-
<img
38-
alt={getPokemonQuery.data.name}
39-
src={`https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/other/official-artwork/${getPokemonQuery.data.id}.png`}
40-
/>
44+
</div>
45+
46+
<div className='w-full min-w-md rounded-lg border p-4'>
47+
{getPokemonQuery.isLoading && (
48+
<div className='flex h-44 flex-col items-center justify-center gap-2'>
49+
<Loader2Icon className='size-5 animate-spin' />
50+
<p>Loading</p>
4151
</div>
42-
</div>
43-
)}
44-
</>
52+
)}
53+
54+
{getPokemonQuery.error && !getPokemonQuery.isLoading && (
55+
<div className='flex h-44 items-center justify-center'>
56+
<p className='text-destructive'>{getPokemonQuery.error.message}</p>
57+
</div>
58+
)}
59+
60+
{getPokemonQuery.data && !getPokemonQuery.isLoading && !getPokemonQuery.error && (
61+
<div className='flex items-center justify-center gap-10'>
62+
<div className='flex size-44 items-center justify-center'>
63+
<img
64+
alt={getPokemonQuery.data.name}
65+
className='h-44'
66+
src={`https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/other/official-artwork/${getPokemonQuery.data.id}.png`}
67+
/>
68+
</div>
69+
70+
<div className='flex flex-col gap-2'>
71+
<p>
72+
name: <code>{getPokemonQuery.data.name}</code>
73+
</p>
74+
75+
<p>
76+
height: <code>{getPokemonQuery.data.height}</code>
77+
</p>
78+
79+
<p>
80+
weight: <code>{getPokemonQuery.data.weight}</code>
81+
</p>
82+
83+
<p>
84+
experience: <code>{getPokemonQuery.data.base_experience}</code>
85+
</p>
86+
</div>
87+
</div>
88+
)}
89+
</div>
90+
</section>
4591
);
4692
};
4793

‎packages/core/src/hooks/useAsyncEffect/useAsyncEffect.demo.tsx‎

Lines changed: 73 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -1,69 +1,95 @@
1-
import { useAsyncEffect } from '@siberiacancode/reactuse';
2-
import { useState } from 'react';
1+
import { useAsyncEffect, useBoolean, useToggle } from '@siberiacancode/reactuse';
2+
import { Loader2Icon } from 'lucide-react';
3+
import { Fragment, useState } from 'react';
4+
5+
import { cn } from '@/utils/lib';
36

47
interface Pokemon {
5-
abilities: string[];
6-
height: number;
78
id: number;
89
name: string;
9-
types: string[];
10-
weight: number;
1110
}
1211

13-
const getRandomPokemon = async () => {
14-
const randomId = Math.floor(Math.random() * 150) + 1;
15-
const response = await fetch(`https://pokeapi.co/api/v2/pokemon/${randomId}`);
16-
const data = await response.json();
12+
const CHAINS = {
13+
bulbasaur: [1, 2, 3],
14+
charmander: [4, 5, 6],
15+
squirtle: [7, 8, 9]
16+
};
1717

18-
return {
19-
name: data.name,
20-
id: data.id,
21-
height: data.height,
22-
weight: data.weight,
23-
types: data.types.map((type: any) => type.type.name),
24-
abilities: data.abilities.map((ability: any) => ability.ability.name)
25-
} as Pokemon;
18+
const getPokemons = async (ids: number[]) => {
19+
await new Promise((resolve) => setTimeout(resolve, 1000));
20+
return Promise.all(
21+
ids.map((id) => fetch(`https://pokeapi.co/api/v2/pokemon/${id}`).then((res) => res.json()))
22+
);
2623
};
2724

2825
const Demo = () => {
29-
const [pokemon, setPokemon] = useState<Pokemon>();
26+
const [chain, toggleChain] = useToggle<keyof typeof CHAINS>([
27+
'bulbasaur',
28+
'charmander',
29+
'squirtle'
30+
]);
31+
32+
const [pokemons, setPokemons] = useState<Pokemon[]>([]);
33+
const [isLoading, setIsLoading] = useBoolean(true);
3034

3135
useAsyncEffect(async () => {
32-
try {
33-
const newPokemon = await getRandomPokemon();
34-
setPokemon(newPokemon);
35-
} catch (err) {
36-
console.error(err);
37-
}
38-
}, []);
36+
setIsLoading(true);
37+
const data = await getPokemons(CHAINS[chain]);
38+
setPokemons(data);
39+
setIsLoading(false);
40+
}, [chain]);
3941

4042
return (
41-
<>
42-
<div className='flex flex-col gap-4'>
43-
{pokemon && (
44-
<div>
45-
<h3 className='mb-1 text-2xl font-bold capitalize'>
46-
{pokemon.name} #{pokemon.id}
47-
</h3>
43+
<section className='flex min-w-md flex-col gap-4'>
44+
<div data-slot='tabs'>
45+
<div className='mb-6' data-slot='tabs-list'>
46+
{Object.keys(CHAINS).map((name) => (
47+
<button
48+
key={name}
49+
data-state={cn(chain === name && 'active')}
50+
data-variant='tabs-trigger'
51+
type='button'
52+
onClick={() => toggleChain(name as keyof typeof CHAINS)}
53+
>
54+
{name}
55+
</button>
56+
))}
57+
</div>
4858

49-
<div className='flex gap-2 text-sm'>
50-
{pokemon.types.map((type) => (
51-
<code key={type}>{type}</code>
52-
))}
59+
<div data-slot='tabs-content'>
60+
{isLoading && (
61+
<div className='flex h-36 flex-col items-center justify-center gap-2'>
62+
<Loader2Icon className='size-6 animate-spin' />
63+
<p>
64+
Loading <code>evolution</code> chain
65+
</p>
5366
</div>
67+
)}
68+
69+
{!isLoading && (
70+
<div className='flex h-36 items-center justify-between gap-2'>
71+
{pokemons.map((pokemon, index) => (
72+
<Fragment key={pokemon.id}>
73+
<div className='flex flex-col items-center gap-2'>
74+
<div className='flex size-28 items-center justify-center'>
75+
<img
76+
alt={pokemon.name}
77+
className='h-28'
78+
src={`https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/other/official-artwork/${pokemon.id}.png`}
79+
/>
80+
</div>
5481

55-
<div className='mt-4 flex flex-col gap-1'>
56-
<div>
57-
height: <code>{pokemon.height / 10} m</code>
58-
</div>
59-
<div>
60-
weight: <code>{pokemon.weight / 10} kg</code>
61-
</div>
82+
<p className='text-sm capitalize'>{pokemon.name}</p>
83+
</div>
84+
85+
{index < pokemons.length - 1 && <div className='text-xl'>{'>'}</div>}
86+
</Fragment>
87+
))}
6288
</div>
63-
</div>
64-
)}
89+
)}
90+
</div>
6591
</div>
66-
</>
92+
</section>
6793
);
6894
};
6995

‎packages/core/src/hooks/useBattery/useBattery.demo.tsx‎

Lines changed: 63 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,21 @@
11
import { useBattery } from '@siberiacancode/reactuse';
2+
import {
3+
BatteryChargingIcon,
4+
BatteryFullIcon,
5+
BatteryLowIcon,
6+
BatteryMediumIcon,
7+
BatteryWarningIcon,
8+
Loader2Icon
9+
} from 'lucide-react';
10+
11+
import { cn } from '@/utils/lib';
12+
13+
const getBatteryIcon = (level: number) => {
14+
if (level > 80) return BatteryFullIcon;
15+
if (level > 40) return BatteryMediumIcon;
16+
if (level > 15) return BatteryLowIcon;
17+
return BatteryWarningIcon;
18+
};
219

320
const Demo = () => {
421
const battery = useBattery();
@@ -20,29 +37,56 @@ const Demo = () => {
2037

2138
if (battery.value.loading) {
2239
return (
23-
<p>
24-
Battery state: <code>fetching</code>
25-
</p>
40+
<section className='flex justify-center'>
41+
<div className='flex h-96 w-76 items-center justify-center rounded-4xl border'>
42+
<Loader2Icon className='size-5 animate-spin' />
43+
</div>
44+
</section>
2645
);
2746
}
2847

48+
const level = Math.round((battery.value.level ?? 0) * 100);
49+
const Icon = battery.value.charging ? BatteryChargingIcon : getBatteryIcon(level);
50+
const lowBattery = level <= 15 && !battery.value.charging;
51+
2952
return (
30-
<>
31-
<p>
32-
Charge level: <code>{battery.value.level && (battery.value.level * 100).toFixed(0)}%</code>
33-
</p>
34-
<p>
35-
Charging: <code>{battery.value.charging ? 'yes' : 'no'}</code>
36-
</p>
37-
<p>
38-
Charging time:{' '}
39-
<code>{battery.value.chargingTime ? battery.value.chargingTime : 'finished'}</code>
40-
</p>
41-
<p>
42-
Discharging time:{' '}
43-
<code>{battery.value.dischargingTime ? battery.value.dischargingTime : 'never'}</code>
44-
</p>
45-
</>
53+
<section className='flex justify-center'>
54+
<div className='flex w-76 flex-col gap-7 rounded-4xl border px-6 pt-4 pb-8'>
55+
<div className='mb-2 flex items-center justify-between'>
56+
<div className='w-12' />
57+
58+
<div className='bg-muted h-1 w-16 rounded-full' />
59+
60+
<div
61+
className={cn(
62+
'flex items-center justify-end gap-1 text-xs',
63+
lowBattery ? 'text-red-500' : 'text-foreground'
64+
)}
65+
>
66+
<span>{level}%</span>
67+
<Icon className='size-5' />
68+
</div>
69+
</div>
70+
71+
<div className='flex flex-col gap-6'>
72+
<div className='flex flex-col gap-2'>
73+
<h2>Feedback</h2>
74+
75+
<p className='text-xs'>
76+
Battery has a lot of information, you can use it to get the battery level, charging
77+
status <code>{battery.value.charging ? 'charging' : 'discharging'}</code>, discharging
78+
time <code>{battery.value.dischargingTime}</code> and more.
79+
</p>
80+
</div>
81+
82+
<form className='flex flex-col gap-4'>
83+
<textarea placeholder='Tell us what can be improved...' rows={4} />
84+
85+
<button type='button'>Send feedback</button>
86+
</form>
87+
</div>
88+
</div>
89+
</section>
4690
);
4791
};
4892

0 commit comments

Comments
 (0)