mirror of
https://github.com/timmypidashev/web.git
synced 2026-04-14 19:13:51 +00:00
Write up my first post
This commit is contained in:
BIN
src/public/blog/my-first-post/thumbnail.png
Normal file
BIN
src/public/blog/my-first-post/thumbnail.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 6.8 MiB |
@@ -17,17 +17,21 @@ interface BlogPostListProps {
|
||||
}
|
||||
|
||||
const formatDate = (dateString: string) => {
|
||||
return new Date(dateString).toLocaleDateString('en-US', {
|
||||
year: 'numeric',
|
||||
month: 'long',
|
||||
day: 'numeric'
|
||||
return new Date(dateString).toLocaleDateString("en-US", {
|
||||
year: "numeric",
|
||||
month: "long",
|
||||
day: "numeric"
|
||||
});
|
||||
};
|
||||
|
||||
export const BlogPostList = ({ posts }: BlogPostListProps) => {
|
||||
return (
|
||||
<div className="w-full max-w-4xl mx-auto">
|
||||
<h1 className="text-3xl md:text-4xl font-bold mb-6 md:mb-10 text-yellow-bright px-4 md:px-0">Blog Posts</h1>
|
||||
<div className="w-full max-w-6xl mx-auto pt-24 sm:pt-24">
|
||||
<h1 className="text-2xl sm:text-3xl font-bold text-purple mb-12 text-center px-4 leading-relaxed">
|
||||
Latest Thoughts <br className="sm:hidden" />
|
||||
& Writings
|
||||
</h1>
|
||||
|
||||
<ul className="space-y-6 md:space-y-10">
|
||||
{posts.map((post) => (
|
||||
<li key={post.slug} className="group px-4 md:px-0">
|
||||
@@ -39,7 +43,7 @@ export const BlogPostList = ({ posts }: BlogPostListProps) => {
|
||||
{/* Image container with fixed aspect ratio */}
|
||||
<div className="w-full md:w-1/3 aspect-[16/9] overflow-hidden rounded-lg bg-background">
|
||||
<img
|
||||
src={post.data.image || '/api/placeholder/400/300'}
|
||||
src={post.data.image || "/blog/placeholder.png"}
|
||||
alt={post.data.title}
|
||||
className="w-full h-full object-cover group-hover:scale-105 transition-transform duration-300"
|
||||
/>
|
||||
|
||||
@@ -21,9 +21,11 @@ export function ProjectList({ projects }: ProjectListProps) {
|
||||
<h2 className="text-xl font-bold text-foreground/90 mb-6">
|
||||
Featured Projects
|
||||
</h2>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6 auto-rows-fr justify-items-center">
|
||||
{latestProjects.map(project => (
|
||||
<ProjectCard key={project.slug} project={project} />
|
||||
<div key={project.slug} className="w-full max-w-md">
|
||||
<ProjectCard project={project} />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
@@ -33,9 +35,11 @@ export function ProjectList({ projects }: ProjectListProps) {
|
||||
<h2 className="text-xl font-bold text-foreground/90 mb-6">
|
||||
All Projects
|
||||
</h2>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6 auto-rows-fr justify-items-center">
|
||||
{otherProjects.map(project => (
|
||||
<ProjectCard key={project.slug} project={project} />
|
||||
<div key={project.slug} className="w-full max-w-md">
|
||||
<ProjectCard project={project} />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
59
src/src/content/blog/components/my-first-post/cookie.tsx
Normal file
59
src/src/content/blog/components/my-first-post/cookie.tsx
Normal file
@@ -0,0 +1,59 @@
|
||||
import { useState } from "react";
|
||||
|
||||
const Cookie = () => {
|
||||
const [hasBite, setHasBite] = useState(false);
|
||||
|
||||
const handleClick = () => {
|
||||
setHasBite((prev) => !prev);
|
||||
};
|
||||
|
||||
return (
|
||||
<button
|
||||
onClick={handleClick}
|
||||
aria-label="Click for a cookie bite"
|
||||
className="inline-flex items-center justify-center p-0 bg-transparent hover:scale-110 transition-transform"
|
||||
style={{
|
||||
verticalAlign: "middle",
|
||||
lineHeight: 0, // Ensure the button doesn't affect the text spacing
|
||||
}}
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 64 64"
|
||||
className="w-6 h-6" // Adjust size to align better with text
|
||||
fill="none"
|
||||
>
|
||||
{/* Base cookie */}
|
||||
<circle cx="32" cy="32" r="30" fill="#D2691E" />
|
||||
|
||||
{/* Bite area: uses a mask */}
|
||||
<mask id="biteMask">
|
||||
{/* Full circle mask */}
|
||||
<rect width="64" height="64" fill="white" />
|
||||
{hasBite && <circle cx="48" cy="16" r="12" fill="black" />}
|
||||
</mask>
|
||||
|
||||
{/* Cookie with bite mask applied */}
|
||||
<circle cx="32" cy="32" r="30" fill="#D2691E" mask="url(#biteMask)" />
|
||||
|
||||
{/* Chocolate chips */}
|
||||
<circle cx="20" cy="24" r="3" fill="#3E2723" />
|
||||
<circle cx="40" cy="18" r="3" fill="#3E2723" />
|
||||
<circle cx="28" cy="40" r="3" fill="#3E2723" />
|
||||
<circle cx="44" cy="36" r="3" fill="#3E2723" />
|
||||
<circle cx="24" cy="48" r="3" fill="#3E2723" />
|
||||
</svg>
|
||||
</button>
|
||||
);
|
||||
};
|
||||
|
||||
const App = () => {
|
||||
return (
|
||||
<p className="text-lg">
|
||||
On your way out, don't forget to grab a cookie!{" "}
|
||||
<Cookie />
|
||||
</p>
|
||||
);
|
||||
};
|
||||
|
||||
export default App;
|
||||
@@ -1,141 +0,0 @@
|
||||
---
|
||||
title: Generics with TypeScript
|
||||
description: A quick introduction to generics with TypeScript
|
||||
author: Timothy Pidashev
|
||||
tags: [typescript]
|
||||
date: December 25, 2021
|
||||
image: "/path/to/your/image.jpg"
|
||||
---
|
||||
|
||||
In this quick post we'll cover what are generics and how to use them with TypeScript. It answers a question I got from a friend, and I thought it could also be useful for others. We'll go through the following points:
|
||||
|
||||
- What are generics
|
||||
- Using generic types
|
||||
- Generics constraints
|
||||
- Generics with functions
|
||||
|
||||
If you're familiar with the concept of code reuse, well it is just that. We can use generics to create reusable patterns for types. It results in flexible types that work with different variations, and also fewer types to maintain as you avoid duplication.
|
||||
|
||||
Code reuse is an important part of building flexible components/functions that serve complex and large systems. Nevertheless, abstraction is hard to get right in general and could cause inflexibility in some cases.
|
||||
|
||||
## What are generics
|
||||
|
||||
You can think of generics as arguments to your types. When a type with generics is used, those generics are passed to it. As we mentioned above, this will allow us to reuse the common patterns of the type declarations.
|
||||
|
||||
Many functions that you're already using could have optional generic types. For example the array methods like `Array.map()` and `Array.reduc()` accept a generic for elements type:
|
||||
|
||||
```ts
|
||||
const numbers = [1, 2, 3]
|
||||
numbers.map((n) => n)
|
||||
numbers.map<number>((n) => n) // with a generic <number> type
|
||||
```
|
||||
|
||||
You probably also came across generics while fixing Typescript type errors, without knowing what's going on. We've all been there. Once you learn about generics, you'll see them used everywhere, in the web APIs and third-party libraries.
|
||||
|
||||
Hope that by the end of this post it’ll be more clear to you.
|
||||
|
||||
## Generic types
|
||||
|
||||
We can start by building our first type with generics. Let's say you have two functions `getUser` and `getProduct` that return some data for a user and a product:
|
||||
|
||||
```ts
|
||||
type User = {
|
||||
email: string
|
||||
}
|
||||
|
||||
type Product = {
|
||||
id: string
|
||||
}
|
||||
|
||||
type UserResponse = {
|
||||
data: User
|
||||
}
|
||||
|
||||
type ProductResponse = {
|
||||
data: Product
|
||||
}
|
||||
|
||||
const user: UserResponse = getUser(id)
|
||||
const product: ProductResponse = getProduct(id)
|
||||
```
|
||||
|
||||
You'll notice that the `UserResponse` and `ProductResponse` are kind of the same type declaration, both have `data` key but with different types. You can expect that if a new type is added it will also need to have its own `YetAnotherResponse` type which will result in duplicating same response type over and over again. And in the case you want to make a change across `...Responses` types, good luck with that.
|
||||
|
||||
We can abstract this common pattern, so our `Response` will be a generic type and depending on the `Data` type passed, it will give the corresponding `Response` type.
|
||||
|
||||
```ts
|
||||
type User = {
|
||||
email: string
|
||||
}
|
||||
|
||||
type Product = {
|
||||
id: string
|
||||
}
|
||||
|
||||
type GenericResponse<Data> = {
|
||||
data: Data
|
||||
}
|
||||
|
||||
const user: GenericResponse<User> = getUser(id)
|
||||
const product: GenericResponse<Product> = getProduct(id)
|
||||
```
|
||||
|
||||
## Generics constraints
|
||||
|
||||
You might notice that so far the generic type accepts any type. That could be what you need, but sometimes we would want to limit or add constraints to a certain type for the generic type passed.
|
||||
|
||||
For a simple example, we can have an `Input` type that has a `Value` generic type. If we want to constrain the `Value` generic type possibilities to be only a `string` or a `number` type, we can specify that by adding an `extends` keyword after the generic name, then the specific constraints type:
|
||||
|
||||
```ts
|
||||
type Input<Value extends string | number> = Value
|
||||
|
||||
const input: Input<string> = 'text' // works
|
||||
const input: Input<number> = 123456 // works
|
||||
const input: Input<object> = {} // has error
|
||||
```
|
||||
|
||||
## Generics with functions
|
||||
|
||||
Generics are part of type definitions. You just need to know how to annotate functions whether it's a function declaration or an arrow function expression:
|
||||
|
||||
```ts
|
||||
function getInput<Input>(input: Input): Input {
|
||||
return input
|
||||
}
|
||||
|
||||
const getInput = <Input>(input: Input): Input => {
|
||||
return input
|
||||
}
|
||||
```
|
||||
|
||||
## Additional example ⌁
|
||||
|
||||
A few months back I wrote an over simplified version of React Query's `useQuery` hook to replace `useEffect` for data fetching in React. Here you'll find a generic `Query` type:
|
||||
|
||||
```ts
|
||||
type Query<Data> = {
|
||||
loading: boolean
|
||||
error: boolean
|
||||
data: Data
|
||||
}
|
||||
|
||||
type Key = string | string[]
|
||||
type Fetcher<Data> = () => Promise<Data>
|
||||
type Options = { enabled?: boolean; cacheTime?: number }
|
||||
|
||||
const useQuery = <Data>(key: Key, fetcher: Fetcher<Data>, options?: Options): Query<Data> => {
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
You can find the entire version of this simplified `useQuery` hook on Render template at [src/hooks/query.ts](https://github.com/oedotme/render/blob/main/src/hooks/query.ts) if you want to take a look on the entire implementation.
|
||||
|
||||
## What's next
|
||||
|
||||
Hope that explained what are generics and how to start using them with TypeScript. You can find more details and examples at [TypeScript generics docs](https://www.typescriptlang.org/docs/handbook/2/generics.html).
|
||||
|
||||
There are very useful [TypeScript utility types](https://www.typescriptlang.org/docs/handbook/utility-types.html), that you'll use generics with. I highly recommend you to check them out, it will help you a lot while using TypeScript in general.
|
||||
|
||||
I would love to hear what you think about this post, feel free to leave a comment on the discussion. If you have questions or got stuck at some point I'll be happy to help.
|
||||
|
||||
Share this post if you find it useful and stay tuned for upcoming posts.
|
||||
24
src/src/content/blog/my-first-post.mdx
Normal file
24
src/src/content/blog/my-first-post.mdx
Normal file
@@ -0,0 +1,24 @@
|
||||
---
|
||||
title: My First Post!
|
||||
description: A quick introduction
|
||||
author: Timothy Pidashev
|
||||
tags: [greeting]
|
||||
date: January 9, 2025
|
||||
image: "/blog/my-first-post/thumbnail.png"
|
||||
---
|
||||
|
||||
import Cookie from "@/content/blog/components/my-first-post/cookie";
|
||||
|
||||
Hello, my name is Timothy Pidashev! I’m an aspiring software engineer currently expanding my technical horizons!
|
||||
|
||||
Wait… that was a bit too formal. This is, after all, a blog post. Let me try that again.
|
||||
|
||||
Hey 👋, my name is Timothy! I’m a software nerd who loves building cool tech! Phew, that was rough. Jokes aside,
|
||||
I’m happy you're here, dear reader. I have a lot planned for my little blog, and I hope the next few posts will
|
||||
be useful to many nerds out there!
|
||||
|
||||
As a little spoiler of what’s to come, the next post will be a complete dive into modifying and corebooting a
|
||||
Thinkpad T440p for the ultimate secure dev laptop! I’ve been personally daily-driving mine for the last two
|
||||
years and can’t wait to show it off, so stay tuned!
|
||||
|
||||
<Cookie />
|
||||
@@ -70,7 +70,6 @@ const jsonLd = {
|
||||
))}
|
||||
</div>
|
||||
<hr class="bg-orange" />
|
||||
<br />
|
||||
<Content />
|
||||
</article>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user