Skip to content

Commit d8f2074

Browse files
feat: implement first recipe tabs
1 parent 1d86f42 commit d8f2074

File tree

11 files changed

+694
-0
lines changed

11 files changed

+694
-0
lines changed
Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
import { Tabs } from '../Tabs';
2+
3+
const pokemonTypes = {
4+
fire: {
5+
name: 'Fire',
6+
color: 'text-red-600',
7+
bgColor: 'bg-red-50',
8+
pokemon: ['Charizard', 'Arcanine', 'Rapidash', 'Flareon']
9+
},
10+
water: {
11+
name: 'Water',
12+
color: 'text-blue-600',
13+
bgColor: 'bg-blue-50',
14+
pokemon: ['Blastoise', 'Gyarados', 'Vaporeon', 'Lapras']
15+
},
16+
grass: {
17+
name: 'Grass',
18+
color: 'text-green-600',
19+
bgColor: 'bg-green-50',
20+
pokemon: ['Venusaur', 'Exeggutor', 'Vileplume', 'Leafeon']
21+
}
22+
};
23+
24+
export const PokemonTabs = () => {
25+
return (
26+
<Tabs
27+
label="Pokemon Types"
28+
initialActiveTabIndex={0}
29+
className="w-full max-w-4xl mx-auto p-6"
30+
id="pokemon-tabs"
31+
>
32+
<Tabs.List className="flex space-x-1 border-b border-gray-200">
33+
<Tabs.Button
34+
className="px-6 py-3 font-semibold rounded-lg transition-all duration-200 bg-gray-100 text-gray-700 hover:bg-gray-200"
35+
activeClassName="px-6 py-3 font-semibold rounded-lg transition-all duration-200 bg-blue-500 text-white"
36+
>
37+
🔥 Fire
38+
</Tabs.Button>
39+
<Tabs.Button
40+
className="px-6 py-3 font-semibold rounded-lg transition-all duration-200 bg-gray-100 text-gray-700 hover:bg-gray-200"
41+
activeClassName="px-6 py-3 font-semibold rounded-lg transition-all duration-200 bg-blue-500 text-white"
42+
>
43+
💧 Water
44+
</Tabs.Button>
45+
<Tabs.Button
46+
className="px-6 py-3 font-semibold rounded-lg transition-all duration-200 bg-gray-100 text-gray-700 hover:bg-gray-200"
47+
activeClassName="px-6 py-3 font-semibold rounded-lg transition-all duration-200 bg-blue-500 text-white"
48+
>
49+
🌿 Grass
50+
</Tabs.Button>
51+
</Tabs.List>
52+
<Tabs.PanelContainer>
53+
<Tabs.Panel className="p-6 bg-white rounded-b-lg border border-gray-200 border-t-0">
54+
<div
55+
className={`${pokemonTypes.fire.bgColor} p-4 rounded-lg`}
56+
>
57+
<h3
58+
className={`text-2xl font-bold ${pokemonTypes.fire.color} mb-4`}
59+
>
60+
🔥 Fire Type Pokemon
61+
</h3>
62+
<p className="text-gray-700 mb-4">
63+
Fire-type Pokémon are known for their fierce attacks and
64+
high offensive capabilities.
65+
</p>
66+
<div className="grid grid-cols-2 gap-3">
67+
{pokemonTypes.fire.pokemon.map((pokemon) => (
68+
<div
69+
key={pokemon}
70+
className="bg-white p-3 rounded-lg shadow-sm border"
71+
>
72+
<span className="font-semibold">{pokemon}</span>
73+
</div>
74+
))}
75+
</div>
76+
</div>
77+
</Tabs.Panel>
78+
<Tabs.Panel className="p-6 bg-white rounded-b-lg border border-gray-200 border-t-0">
79+
<div
80+
className={`${pokemonTypes.water.bgColor} p-4 rounded-lg`}
81+
>
82+
<h3
83+
className={`text-2xl font-bold ${pokemonTypes.water.color} mb-4`}
84+
>
85+
💧 Water Type Pokemon
86+
</h3>
87+
<p className="text-gray-700 mb-4">
88+
Water-type Pokémon are versatile and often have strong
89+
defensive capabilities.
90+
</p>
91+
<div className="grid grid-cols-2 gap-3">
92+
{pokemonTypes.water.pokemon.map((pokemon) => (
93+
<div
94+
key={pokemon}
95+
className="bg-white p-3 rounded-lg shadow-sm border"
96+
>
97+
<span className="font-semibold">{pokemon}</span>
98+
</div>
99+
))}
100+
</div>
101+
</div>
102+
</Tabs.Panel>
103+
<Tabs.Panel className="p-6 bg-white rounded-b-lg border border-gray-200 border-t-0">
104+
<div
105+
className={`${pokemonTypes.grass.bgColor} p-4 rounded-lg`}
106+
>
107+
<h3
108+
className={`text-2xl font-bold ${pokemonTypes.grass.color} mb-4`}
109+
>
110+
🌿 Grass Type Pokemon
111+
</h3>
112+
<p className="text-gray-700 mb-4">
113+
Grass-type Pokémon excel at status effects and often
114+
have healing abilities.
115+
</p>
116+
<div className="grid grid-cols-2 gap-3">
117+
{pokemonTypes.grass.pokemon.map((pokemon) => (
118+
<div
119+
key={pokemon}
120+
className="bg-white p-3 rounded-lg shadow-sm border"
121+
>
122+
<span className="font-semibold">{pokemon}</span>
123+
</div>
124+
))}
125+
</div>
126+
</div>
127+
</Tabs.Panel>
128+
</Tabs.PanelContainer>
129+
</Tabs>
130+
);
131+
};
Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
import { Tabs } from '../Tabs';
2+
3+
const pokemonTypes = {
4+
fire: {
5+
name: 'Fire',
6+
color: 'text-red-600',
7+
bgColor: 'bg-red-50',
8+
pokemon: ['Charizard', 'Arcanine', 'Rapidash', 'Flareon']
9+
},
10+
water: {
11+
name: 'Water',
12+
color: 'text-blue-600',
13+
bgColor: 'bg-blue-50',
14+
pokemon: ['Blastoise', 'Gyarados', 'Vaporeon', 'Lapras']
15+
},
16+
grass: {
17+
name: 'Grass',
18+
color: 'text-green-600',
19+
bgColor: 'bg-green-50',
20+
pokemon: ['Venusaur', 'Exeggutor', 'Vileplume', 'Leafeon']
21+
}
22+
};
23+
24+
export const PokemonVerticalTabs = () => {
25+
return (
26+
<Tabs
27+
label="Pokemon Types"
28+
initialActiveTabIndex={0}
29+
className="w-full max-w-4xl mx-auto p-6 flex gap-6"
30+
id="pokemon-tabs-vertical"
31+
>
32+
<Tabs.List className="flex flex-col space-y-2 min-w-48">
33+
<Tabs.Button
34+
className="px-6 py-3 font-semibold rounded-lg transition-all duration-200 bg-gray-100 text-gray-700 hover:bg-gray-200"
35+
activeClassName="px-6 py-3 font-semibold rounded-lg transition-all duration-200 bg-blue-500 text-white"
36+
>
37+
🔥 Fire
38+
</Tabs.Button>
39+
<Tabs.Button
40+
className="px-6 py-3 font-semibold rounded-lg transition-all duration-200 bg-gray-100 text-gray-700 hover:bg-gray-200"
41+
activeClassName="px-6 py-3 font-semibold rounded-lg transition-all duration-200 bg-blue-500 text-white"
42+
>
43+
💧 Water
44+
</Tabs.Button>
45+
<Tabs.Button
46+
className="px-6 py-3 font-semibold rounded-lg transition-all duration-200 bg-gray-100 text-gray-700 hover:bg-gray-200"
47+
activeClassName="px-6 py-3 font-semibold rounded-lg transition-all duration-200 bg-blue-500 text-white"
48+
>
49+
🌿 Grass
50+
</Tabs.Button>
51+
</Tabs.List>
52+
<Tabs.PanelContainer className="flex-1">
53+
<Tabs.Panel className="p-6 bg-white rounded-lg border border-gray-200">
54+
<div
55+
className={`${pokemonTypes.fire.bgColor} p-4 rounded-lg`}
56+
>
57+
<h3
58+
className={`text-2xl font-bold ${pokemonTypes.fire.color} mb-4`}
59+
>
60+
🔥 Fire Type Pokemon
61+
</h3>
62+
<p className="text-gray-700 mb-4">
63+
Fire-type Pokémon are known for their fierce attacks and
64+
high offensive capabilities.
65+
</p>
66+
<div className="grid grid-cols-2 gap-3">
67+
{pokemonTypes.fire.pokemon.map((pokemon) => (
68+
<div
69+
key={pokemon}
70+
className="bg-white p-3 rounded-lg shadow-sm border"
71+
>
72+
<span className="font-semibold">{pokemon}</span>
73+
</div>
74+
))}
75+
</div>
76+
</div>
77+
</Tabs.Panel>
78+
<Tabs.Panel className="p-6 bg-white rounded-lg border border-gray-200">
79+
<div
80+
className={`${pokemonTypes.water.bgColor} p-4 rounded-lg`}
81+
>
82+
<h3
83+
className={`text-2xl font-bold ${pokemonTypes.water.color} mb-4`}
84+
>
85+
💧 Water Type Pokemon
86+
</h3>
87+
<p className="text-gray-700 mb-4">
88+
Water-type Pokémon are versatile and often have strong
89+
defensive capabilities.
90+
</p>
91+
<div className="grid grid-cols-2 gap-3">
92+
{pokemonTypes.water.pokemon.map((pokemon) => (
93+
<div
94+
key={pokemon}
95+
className="bg-white p-3 rounded-lg shadow-sm border"
96+
>
97+
<span className="font-semibold">{pokemon}</span>
98+
</div>
99+
))}
100+
</div>
101+
</div>
102+
</Tabs.Panel>
103+
<Tabs.Panel className="p-6 bg-white rounded-lg border border-gray-200">
104+
<div
105+
className={`${pokemonTypes.grass.bgColor} p-4 rounded-lg`}
106+
>
107+
<h3
108+
className={`text-2xl font-bold ${pokemonTypes.grass.color} mb-4`}
109+
>
110+
🌿 Grass Type Pokemon
111+
</h3>
112+
<p className="text-gray-700 mb-4">
113+
Grass-type Pokémon excel at status effects and often
114+
have healing abilities.
115+
</p>
116+
<div className="grid grid-cols-2 gap-3">
117+
{pokemonTypes.grass.pokemon.map((pokemon) => (
118+
<div
119+
key={pokemon}
120+
className="bg-white p-3 rounded-lg shadow-sm border"
121+
>
122+
<span className="font-semibold">{pokemon}</span>
123+
</div>
124+
))}
125+
</div>
126+
</div>
127+
</Tabs.Panel>
128+
</Tabs.PanelContainer>
129+
</Tabs>
130+
);
131+
};
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import {
2+
createContext,
3+
Dispatch,
4+
SetStateAction,
5+
useContext,
6+
useState
7+
} from 'react';
8+
9+
const TabsContext = createContext<{
10+
activeTabIndex: number;
11+
setActiveTabIndex: Dispatch<SetStateAction<number>>;
12+
id: string;
13+
} | null>(null);
14+
15+
export interface ITabs {
16+
children: React.ReactNode | React.ReactNode[];
17+
initialActiveTabIndex?: number;
18+
label: string;
19+
className?: string;
20+
id: string;
21+
}
22+
23+
export const Tabs = ({
24+
children,
25+
initialActiveTabIndex = 1,
26+
label,
27+
className = '',
28+
id
29+
}: ITabs) => {
30+
const [activeTabIndex, setActiveTabIndex] = useState<number>(
31+
initialActiveTabIndex
32+
);
33+
34+
return (
35+
<TabsContext.Provider
36+
value={{ activeTabIndex, setActiveTabIndex, id }}
37+
>
38+
<div role="tablist" aria-label={label} className={className}>
39+
{children}
40+
</div>
41+
</TabsContext.Provider>
42+
);
43+
};
44+
45+
export const useTabs = () => {
46+
const context = useContext(TabsContext);
47+
48+
if (!context) {
49+
throw new Error('You need to use this within the TabsContext');
50+
}
51+
52+
return context;
53+
};
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import { useEffect, useRef, useState } from 'react';
2+
3+
export interface ITabsButtonProps {
4+
ariaControls?: string;
5+
id?: string;
6+
isSelected?: boolean;
7+
onClick?: () => void;
8+
children: React.ReactNode | React.ReactNode[];
9+
onKeyUp?: (event: React.KeyboardEvent<HTMLButtonElement>) => void;
10+
className?: string;
11+
activeClassName?: string;
12+
}
13+
14+
export const TabsButton = ({
15+
ariaControls,
16+
id,
17+
isSelected,
18+
onClick,
19+
children,
20+
onKeyUp,
21+
className = ''
22+
}: ITabsButtonProps) => {
23+
const buttonRef = useRef<HTMLButtonElement>(null);
24+
const [isMounted, setIsMounted] = useState(false);
25+
26+
useEffect(() => {
27+
setIsMounted(true);
28+
}, []);
29+
30+
useEffect(() => {
31+
if (isSelected && isMounted) {
32+
buttonRef.current?.focus();
33+
}
34+
}, [isSelected]);
35+
36+
return (
37+
<button
38+
role="tab"
39+
aria-selected={isSelected}
40+
aria-controls={ariaControls}
41+
id={id}
42+
onClick={onClick}
43+
ref={buttonRef}
44+
tabIndex={isSelected ? 0 : -1}
45+
className={className}
46+
onKeyUp={onKeyUp}
47+
>
48+
{children}
49+
</button>
50+
);
51+
};

0 commit comments

Comments
 (0)