Skip to content

Commit 947272d

Browse files
committed
new
1 parent 37b9e58 commit 947272d

File tree

12 files changed

+337
-19
lines changed

12 files changed

+337
-19
lines changed
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
2+
# Demystifying Tailwind CSS in `fezcodex`: A Utility-First Approach
3+
4+
In the `fezcodex` project, you'll notice that our components are styled **cool**(!). Instead of writing custom CSS for every element, **Tailwind CSS** is used. This post will explain what Tailwind CSS is, how it's configured in my project,
5+
and why it's a powerful tool for building user interfaces.
6+
7+
## Part 1: The Core Concept - Utility-First CSS
8+
9+
Traditionally, when styling a webpage, you might write CSS like this:
10+
11+
```css
12+
.my-button {
13+
background-color: blue;
14+
color: white;
15+
padding: 1rem;
16+
border-radius: 0.5rem;
17+
}
18+
```
19+
20+
And then apply it in your HTML:
21+
22+
```html
23+
<button class="my-button">Click Me</button>
24+
```
25+
26+
Tailwind CSS takes a **utility-first** approach. Instead of writing custom CSS classes for every component, you apply small, single-purpose utility classes directly in your HTML (or JSX, in our case). Each class does one thing, and one thing only.
27+
28+
For example, the same button in Tailwind would look like this:
29+
30+
```html
31+
<button class="bg-blue-500 text-white p-4 rounded-md">Click Me</button>
32+
```
33+
34+
**Benefits of Utility-First CSS:**
35+
36+
* **Rapid UI Development:** You can build complex UIs much faster because you're not constantly switching between HTML/JSX and CSS files. All the styling happens directly in your markup.
37+
* **Consistent Design:** By using a predefined set of utility classes (which are based on a design system), it's much easier to maintain a consistent look and feel across your application.
38+
* **No More Unused CSS:** Tailwind, especially with its JIT (Just-In-Time) mode, only generates the CSS that you actually use in your project. This results in incredibly small and optimized CSS bundles, improving performance.
39+
* **Avoid Naming Headaches:** You no longer have to come up with semantic class names for every single element, which can be a surprisingly difficult task in larger projects.
40+
41+
## Part 2: Tailwind in `fezcodex` - Configuration and Customization
42+
43+
Our project customizes Tailwind to fit its specific design needs. This is primarily managed through two files:
44+
45+
### `tailwind.config.js`
46+
47+
This is the central configuration file for Tailwind CSS. It tells Tailwind how to behave and what custom styles to include.
48+
49+
* **`content`**: This array (`./src/**/*.{js,jsx,ts,tsx}`) tells Tailwind which files to scan for utility classes. This is crucial for the build process to identify and include only the necessary CSS.
50+
* **`theme.extend.colors`**: This is where we integrate our custom color palette. You'll see it imports `colors` from `./src/config/colors.js`. This means that any color defined in `colors.js` (like `article: '#FA8072'`) becomes available as a Tailwind utility class. For example:
51+
* `text-article` will apply the `article` color to text.
52+
* `bg-article` will apply the `article` color to the background.
53+
* `border-article` will apply the `article` color to the border.
54+
This is why we use `text-article` and not just `article` – the `text-` prefix tells Tailwind *what* CSS property to apply the color to.
55+
* **`theme.extend.fontFamily`**: Similar to colors, this section allows us to define and use custom fonts (imported from `./src/config/fonts.js`) throughout the project using Tailwind's `font-{name}` classes.
56+
* **`plugins`**: We use `@tailwindcss/typography` here. This plugin provides a set of `prose` classes that can be used to style raw HTML (like the content generated from Markdown files) with beautiful, readable typography, without having to manually style every heading, paragraph, and list item.
57+
58+
### `src/config/colors.js` and `src/config/fonts.js`
59+
60+
These files act as our project's design token repositories. They centralize all our custom colors and font definitions, making it easy to manage and update our design system from a single source.
61+
62+
## Part 3: How It All Comes Together - Building UI
63+
64+
When you look at a component like `AppCard.js` or `WordCounterPage.js`, you'll see a lot of classes directly in the JSX. For example, a card might have classes like:
65+
66+
```html
67+
<div class="bg-transparent border rounded-lg shadow-lg p-6 flex flex-col justify-between relative transform transition-all duration-300 ease-in-out hover:scale-105 hover:shadow-2xl overflow-hidden h-full">
68+
<!-- ... content ... -->
69+
</div>
70+
```
71+
72+
Let's break down a few of these:
73+
74+
* `bg-transparent`: Sets the background to transparent.
75+
* `border`: Adds a default border.
76+
* `rounded-lg`: Applies a large border-radius.
77+
* `shadow-lg`: Adds a large box shadow.
78+
* `p-6`: Adds padding of `1.5rem` on all sides.
79+
* `flex flex-col justify-between`: Configures the element as a flex container, arranging its children in a column and distributing space between them.
80+
* `hover:scale-105 hover:shadow-2xl`: These are **variant** classes. They apply `scale-105` (makes the element 5% larger) and `shadow-2xl` (a larger shadow) *only when the element is hovered over*.
81+
* `transition-all duration-300 ease-in-out`: Ensures that changes to properties like `transform` (for `scale`) and `box-shadow` (for `shadow`) happen smoothly over 300 milliseconds.
82+
83+
Tailwind also makes responsive design easy with prefixes like `sm:`, `md:`, `lg:`, and `xl:`. For example, `md:flex` would make an element a flex container only on medium screens and larger.
84+
85+
## Part 4: The Build Process
86+
87+
During development and when building for production, a tool like **Craco** (which sits on top of Create React App's Webpack configuration) processes your code. It uses **PostCSS** and the Tailwind plugin to scan all your files for Tailwind utility classes. It then generates a minimal CSS file containing *only* the styles corresponding to the classes you've actually used. This ensures that your final application bundle is as small and performant as possible.
88+
89+
## Conclusion
90+
91+
Tailwind CSS provides a powerful and efficient way to build and maintain the UI of the `fezcodex` project. By embracing its utility-first philosophy and leveraging its extensive configuration options, we can rapidly develop consistent, responsive, and performant user interfaces. It streamlines the styling process, allowing developers to focus more on functionality and less on managing complex CSS stylesheets.

public/posts/posts.json

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,14 @@
11
[
2+
{
3+
"slug": "demystifying-tailwind-css",
4+
"title": "Demystifying Tailwind CSS",
5+
"date": "2025-11-07",
6+
"updated": "2025-11-07",
7+
"description": "Demystifying Tailwind CSS",
8+
"tags": ["tailwind", "css", "fezcodex", "react"],
9+
"category": "dev",
10+
"filename": "demystifying-tailwind-css.txt"
11+
},
212
{
313
"slug": "ubuntu-once-more",
414
"title": "Ubuntu Once More",
@@ -19,16 +29,24 @@
1929
{
2030
"slug": "react-memoization-hooks",
2131
"title": "React Memoization Hooks",
22-
"filename": "/react-of-fezcode/016-react-memoization-hooks.txt",
32+
"filename": "/react-of-fezcode/017-react-memoization-hooks.txt",
2333
"date": "2025-10-25",
2434
"updated": "2025-10-26",
2535
"category": "dev"
2636
},
2737
{
2838
"slug": "react-refs-useref",
2939
"title": "React Refs Useref",
30-
"filename": "/react-of-fezcode/015-react-refs-useref.txt",
40+
"filename": "/react-of-fezcode/016-react-refs-useref.txt",
41+
"date": "2025-10-25",
42+
"category": "dev"
43+
},
44+
{
45+
"slug": "react-toast-explanation-in-details",
46+
"title": "How React Toasts Work in `fezcodex`",
47+
"filename": "/react-of-fezcode/015-react-toast-explanation-in-details.txt",
3148
"date": "2025-10-25",
49+
"updated": "2025-10-25",
3250
"category": "dev"
3351
},
3452
{
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
2+
# Deep Dive: How React Toasts Work in `fezcodex`
3+
4+
Toast notifications are a staple of modern web applications. They provide non-intrusive feedback to users about the result of their actions. In the `fezcodex` project, we have a robust and reusable toast system. This article will break down how it works, from its architecture to the React magic that holds it all together.
5+
6+
## Part 1: The Architecture - A Tale of Three Components
7+
8+
The toast system is elegantly designed around three key parts that work in harmony:
9+
10+
1. **`ToastProvider.js` (The Brains):** This is the central manager. It wraps our entire application, creating a "context" that any component can plug into. It holds the list of all active toasts and provides the functions (`addToast`, `removeToast`) to modify that list. It's also responsible for rendering the container where the toasts appear.
11+
12+
2. **`useToast.js` (The Public API):** This is a custom React Hook that acts as a clean and simple gateway. Instead of components needing to know about the underlying context, they can just use this hook to get access to the `addToast` function. It's the "button" that other components press to request a toast.
13+
14+
3. **`Toast.js` (The Notification UI):** This component represents a single toast message. It's responsible for its own appearance, animations, and, most importantly, its own demise. It knows how long it should be on screen and contains the logic to remove itself after its time is up.
15+
16+
## Part 2: The Magic of `useState` - Where Does the State Go?
17+
18+
This is the crucial question. In `ToastProvider.js`, we have this line:
19+
20+
```javascript
21+
const [toasts, setToasts] = useState([]);
22+
```
23+
24+
When a component function runs, all its internal variables are created and then discarded when it's done. So how does the `toasts` array not just reset to `[]` every single time?
25+
26+
**React Remembers.**
27+
28+
The `useState` hook is a request to React to create and manage a piece of state *on behalf of* your component.
29+
30+
1. **First Render:** The very first time `ToastProvider` renders, React sees `useState([])`. It creates a "memory cell" for this specific component instance and puts an empty array `[]` inside it. It then returns that array to the component as the `toasts` variable.
31+
32+
2. **State Updates:** When you call `addToast`, it eventually calls `setToasts(...)`. This function doesn't change the state directly. Instead, it sends a message to React saying, "I have a new value for this state. Please update it and re-render the component."
33+
34+
3. **Subsequent Renders:** When React re-renders `ToastProvider`, it arrives at the `useState([])` line again. But this time, React knows it has already created a state for this component. It **ignores the initial value (`[]`)** and instead provides the *current* value from its internal memory—the updated array of toasts.
35+
36+
This is the fundamental principle of React Hooks: they allow your function components to have stateful logic that persists across renders, managed by React itself.
37+
38+
## Part 3: The Full Lifecycle of a Toast
39+
40+
Let's tie it all together by following a single toast from birth to death.
41+
42+
1. **The Call:** A user performs an action in a component (e.g., the Word Counter). That component calls `addToast({ title: 'Success!', ... })`.
43+
44+
2. **The Context:** The `useToast` hook provides the `addToast` function from the `ToastProvider`'s context.
45+
46+
3. **The State Update:** The `addToast` function in `ToastProvider` runs. It creates a new toast object with a unique ID and calls `setToasts([newToast, ...otherToasts])`.
47+
48+
4. **The Re-render:** React receives the state update request and schedules a re-render for `ToastProvider`.
49+
50+
5. **The Render:** `ToastProvider` runs again. It calls `useState`, and React hands it the **new array** containing our new toast. The component's `return` statement is executed, and its `.map()` function now loops over an array that includes the new toast.
51+
52+
6. **The Birth:** A new `<Toast />` component is rendered on the screen. It receives its `id`, `title`, `message`, and `duration` as props.
53+
54+
7. **The Countdown:** Inside the new `<Toast />` component, a `useEffect` hook fires. It starts a `setTimeout` timer for the given `duration`.
55+
56+
8. **The End:** When the timer finishes, it calls the `removeToast(id)` function that was passed down as a prop.
57+
58+
9. **The Cleanup:** `removeToast` in the `ToastProvider` calls `setToasts(...)` again, this time with an array that *filters out* the toast with the matching ID.
59+
60+
10. **The Final Re-render:** React processes the state update, re-renders the `ToastProvider`, and the toast is no longer in the array. It vanishes from the screen.
61+
62+
## Conclusion
63+
64+
The `fezcodex` toast system is a perfect microcosm of modern React development. It shows how to use **Context** to provide global functionality without cluttering components, and it relies on the magic of the **`useState` hook** to give components a memory that persists between renders. By letting React manage the state, we can write declarative UI that simply reacts to state changes.

public/posts/react-of-fezcode/015-react-refs-useref.txt renamed to public/posts/react-of-fezcode/016-react-refs-useref.txt

File renamed without changes.

public/posts/react-of-fezcode/016-react-memoization-hooks.txt renamed to public/posts/react-of-fezcode/017-react-memoization-hooks.txt

File renamed without changes.

src/components/AnimatedRoutes.js

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import DndLorePage from '../pages/DndLorePage'; // New import
1818
import DndBookPage from '../pages/DndBookPage'; // New import
1919
import AppPage from '../pages/AppPage';
2020
import IpPage from '../pages/apps/IpPage';
21+
import WordCounterPage from '../pages/apps/WordCounterPage';
2122

2223
import UsefulLinksPage from '../pages/UsefulLinksPage';
2324

@@ -300,6 +301,20 @@ function AnimatedRoutes() {
300301
</motion.div>
301302
}
302303
/>
304+
<Route
305+
path="/apps/word-counter"
306+
element={
307+
<motion.div
308+
initial="initial"
309+
animate="in"
310+
exit="out"
311+
variants={pageVariants}
312+
transition={pageTransition}
313+
>
314+
<WordCounterPage />
315+
</motion.div>
316+
}
317+
/>
303318
{/* D&D specific 404 page */}
304319
<Route
305320
path="/dnd/*"

src/components/AppCard.js

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,17 +7,17 @@ const AppCard = ({ app }) => {
77
const { to, title, description } = app;
88

99
const cardStyle = {
10-
backgroundColor: colors['article-alpha-10'],
11-
borderColor: colors['article-alpha-50'],
12-
color: colors.article,
10+
backgroundColor: colors['app-alpha-10'],
11+
borderColor: colors['app-alpha-50'],
12+
color: colors.app,
1313
};
1414

15-
const detailTextColor = colors['article-light'];
15+
const detailTextColor = colors['app-light'];
1616

1717
return (
1818
<Link to={to} className="block h-full">
1919
<div
20-
className="group bg-transparent border rounded-lg shadow-2xl p-6 flex flex-col justify-between relative transform transition-all duration-300 ease-in-out scale-105 overflow-hidden h-full"
20+
className="group bg-transparent border rounded-lg shadow-lg p-6 flex flex-col justify-between relative transform transition-all duration-300 ease-in-out hover:scale-105 hover:shadow-2xl overflow-hidden h-full"
2121
style={cardStyle}
2222
>
2323
<div

src/components/Sidebar.js

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ const Sidebar = ({ isOpen, toggleSidebar, toggleModal }) => {
6565
const getLinkClass = ({ isActive }) =>
6666
`flex items-center space-x-3 px-3 py-1 rounded-md transition-colors ${
6767
isActive
68-
? 'text-article bg-article-alpha-10'
68+
? 'text-sidebar-highlight bg-sidebar-highlight-alpha-10'
6969
: 'text-gray-100 hover:text-white hover:bg-gray-800'
7070
}`;
7171

@@ -120,10 +120,10 @@ const Sidebar = ({ isOpen, toggleSidebar, toggleModal }) => {
120120
<button
121121
onClick={() => setIsMainOpen(!isMainOpen)}
122122
className={`flex items-center justify-between w-full text-sm font-normal uppercase tracking-wider mb-4 focus:outline-none ${
123-
isMainActive ? 'text-article' : 'text-gray-100'
123+
isMainActive ? 'text-sidebar-highlight' : 'text-gray-100'
124124
}`}
125125
>
126-
<span className={`flex items-center gap-2 font-sans ${isMainActive ? 'text-article' : 'text-white'}`}>
126+
<span className={`flex items-center gap-2 font-sans ${isMainActive ? 'text-sidebar-highlight' : 'text-white'}`}>
127127
<AsteriskSimpleIcon size={16} />
128128
<span>Main</span>
129129
</span>
@@ -155,10 +155,10 @@ const Sidebar = ({ isOpen, toggleSidebar, toggleModal }) => {
155155
<button
156156
onClick={() => setIsContentOpen(!isContentOpen)}
157157
className={`flex items-center justify-between w-full text-sm font-normal uppercase tracking-wider mb-4 focus:outline-none ${
158-
isContentActive ? 'text-article' : 'text-gray-100'
158+
isContentActive ? 'text-sidebar-highlight' : 'text-gray-100'
159159
}`}
160160
>
161-
<span className={`flex items-center gap-2 font-sans ${isContentActive ? 'text-article' : 'text-white'}`}>
161+
<span className={`flex items-center gap-2 font-sans ${isContentActive ? 'text-sidebar-highlight' : 'text-white'}`}>
162162
<BooksIcon size={16} />
163163
<span>Content</span>
164164
</span>
@@ -196,10 +196,10 @@ const Sidebar = ({ isOpen, toggleSidebar, toggleModal }) => {
196196
<button
197197
onClick={() => setIsAppsOpen(!isAppsOpen)}
198198
className={`flex items-center justify-between w-full text-sm font-normal uppercase tracking-wider mb-4 focus:outline-none ${
199-
isAppsActive ? 'text-article' : 'text-gray-100'
199+
isAppsActive ? 'text-sidebar-highlight' : 'text-gray-100'
200200
}`}
201201
>
202-
<span className={`flex items-center gap-2 font-sans ${isAppsActive ? 'text-article' : 'text-white'}`}>
202+
<span className={`flex items-center gap-2 font-sans ${isAppsActive ? 'text-sidebar-highlight' : 'text-white'}`}>
203203
<SquaresFourIcon size={16} />
204204
<span>Apps</span>
205205
</span>

src/config/colors.js

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ module.exports = {
1111
movie: '#c75146',
1212
game: '#80ed99',
1313
article: '#FA8072', // Salmon
14+
app: '#9EC5AB', // Duplicated from book
1415
music: '#A07B90',
1516
series: '#edc531',
1617
food: '#F4A259',
@@ -20,11 +21,16 @@ module.exports = {
2021
'movie-light': '#E08070',
2122
'game-light': '#B2F5C2',
2223
'article-light': '#FFB6C1', // Light Pink
24+
'app-light': '#FFF8F00', // Duplicated from book-light
2325
'music-light': '#C0A0B0',
2426
'series-light': '#F5E080',
2527
'food-light': '#FFC780',
2628
'websites-light': '#FFF8DC',
2729
'tools-light': '#F0DFFF',
30+
'sidebar-highlight': '#FA8072', // Duplicated from article
31+
'sidebar-highlight-light': '#FFB6C1', // Duplicated from article-light
32+
'sidebar-highlight-alpha-10': 'rgba(250, 128, 114, 0.1)', // Duplicated from article-alpha-10
33+
'sidebar-highlight-alpha-50': 'rgba(250, 128, 114, 0.5)', // Duplicated from article-alpha-50
2834
'title-hover': '#fdd4a6', // orange-200
2935
'markdown-title-color': '#fed7aa', // orange-200
3036
'markdown-hx-color': '#ffedd5', // orange-100
@@ -48,6 +54,8 @@ module.exports = {
4854
'game-alpha-50': 'rgba(0, 255, 127, 0.5)',
4955
'article-alpha-10': 'rgba(250, 128, 114, 0.1)',
5056
'article-alpha-50': 'rgba(250, 128, 114, 0.5)',
57+
'app-alpha-10': 'rgba(158, 197, 171, 0.1)', // Duplicated from book-alpha-10
58+
'app-alpha-50': 'rgba(158, 197, 171, 0.5)', // Duplicated from book-alpha-50
5159
'music-alpha-10': 'rgba(160, 123, 144, 0.1)',
5260
'music-alpha-50': 'rgba(160, 123, 144, 0.5)',
5361
'series-alpha-10': 'rgba(237, 197, 49, 0.1)',
@@ -114,4 +122,4 @@ module.exports = {
114122
'line-highlight-start': '#3c526d5f',
115123
'line-highlight-end': '#3c526d55',
116124
},
117-
};
125+
};

src/pages/AppPage.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,11 @@ import AppCard from '../components/AppCard';
55
import usePageTitle from '../utils/usePageTitle';
66

77
const apps = [
8+
{
9+
to: '/apps/word-counter',
10+
title: 'Word Counter',
11+
description: 'Count words, characters, lines and paragraphs in a text.',
12+
},
813
{
914
to: '/apps/ip',
1015
title: 'Show my IP',

0 commit comments

Comments
 (0)