/ Gists

Gists

On gists

App.j

App.j #

_

On gists

Variants - TW3 / TW4

Tailwind CSS

variants #



// TW 3
@layer components {
  .x {
    color: red;
    border: 1px solid red;
  }
}


// TW 4 
@utility x {
  color: red;
  border: 1px solid red;
}



<div class="[&_ul]:x">
  <ul>
    <li>Test</li>
  </ul>
</div>

On gists

Forms in React

React

StateForm.jsx #

import { useState, useMemo } from "react"
import { checkEmail, checkPassword } from "./validators"

export function StateForm() {
  const [email, setEmail] = useState("")
  const [password, setPassword] = useState("")
  const [isAfterFirstSubmit, setIsAfterFirstSubmit] = useState(false)

  const emailErrors = useMemo(() => {
    return isAfterFirstSubmit ? checkEmail(email) : []
  }, [isAfterFirstSubmit, email])

  const passwordErrors = useMemo(() => {
    return isAfterFirstSubmit ? checkPassword(password) : []
  }, [isAfterFirstSubmit, password])

  function onSubmit(e) {
    e.preventDefault()
    setIsAfterFirstSubmit(true)

    const emailResults = checkEmail(email)
    const passwordResults = checkPassword(password)

    if (emailResults.length === 0 && passwordResults.length === 0) {
      alert("Success")
    }
  }

  return (
    <form onSubmit={onSubmit} className="form">
      <div className={`form-group ${emailErrors.length > 0 ? "error" : ""}`}>
        <label className="label" htmlFor="email">
          Email
        </label>
        <input
          className="input"
          type="email"
          id="email"
          value={email}
          onChange={e => setEmail(e.target.value)}
        />
        {emailErrors.length > 0 && (
          <div className="msg">{emailErrors.join(", ")}</div>
        )}
      </div>
      <div className={`form-group ${passwordErrors.length > 0 ? "error" : ""}`}>
        <label className="label" htmlFor="password">
          Password
        </label>
        <input
          className="input"
          type="password"
          id="password"
          value={password}
          onChange={e => setPassword(e.target.value)}
        />
        {passwordErrors.length > 0 && (
          <div className="msg">{passwordErrors.join(", ")}</div>
        )}
      </div>
      <button className="btn" type="submit">
        Submit
      </button>
    </form>
  )
}

On gists

useLocalStorage

React

useLocalStorage.js #

import { useEffect, useState } from "react"

export function useLocalStorage(key, initialValue) {
  const [value, setValue] = useState(() => {
    const localValue = localStorage.getItem(key)
    if (localValue == null) {
      if (typeof initialValue === "function") {
        return initialValue()
      } else {
        return initialValue
      }
    } else {
      return JSON.parse(localValue)
    }
  })

  useEffect(() => {
    if (value === undefined) {
      localStorage.removeItem(key)
    } else {
      localStorage.setItem(key, JSON.stringify(value))
    }
  }, [value, key])

  return [value, setValue]
}

On gists

useArray custom hook

React

useArray.js #

import { useState, useCallback } from "react"

export function useArray(initialValue) {
  const [array, setArray] = useState(initialValue)

  const push = useCallback(element => {
    setArray(a => [...a, element])
  }, [])

  const replace = useCallback((index, newElement) => {
    setArray(a => {
      return [...a.slice(0, index), newElement, ...a.slice(index + 1)]
    })
  }, [])

  const filter = useCallback(callback => {
    setArray(a => {
      return a.filter(callback)
    })
  }, [])

  const remove = useCallback(index => {
    setArray(a => {
      return [...a.slice(0, index), ...a.slice(index + 1)]
    })
  }, [])

  const clear = useCallback(() => {
    setArray([])
  }, [])

  const reset = useCallback(() => {
    setArray(initialValue)
  }, [initialValue])

  return { array, set: setArray, push, replace, filter, remove, clear, reset }
}

On gists

Fetch userlist with handling via AbortController

React

App.jsx #

import { useEffect, useState } from "react"
import { User } from "./User"

export default function App() {
  const [users, setUsers] = useState([])
  const [error, setError] = useState(null)
  const [status, setStatus] = useState("idle")

  useEffect(() => {
    setStatus("loading")
    setUsers([])
    setError(null)

    const controller = new AbortController()
    fetch("https://jsonplaceholder.typicode.com/users", {
      signal: controller.signal,
    })
      .then(res => {
        if (res.ok) return res.json()
        throw new Error(`Status code: ${res.status}`)
      })
      .then(data => {
        setUsers(data)
        setStatus("fetched")
        setError(null)
      })
      .catch(err => {
        if (err.name === "AbortError") return
        setError(err)
        setUsers([])
        setStatus("error")
      })

    return () => {
      controller.abort()
    }
  }, [])

  return (
    <>
      <h1>User List</h1>
      {status === "loading" && <h2>Loading...</h2>}
      {status === "error" && (
        <>
          <h2>Error fetching users</h2>
          <p>{error.message}</p>
        </>
      )}
      {status === "fetched" && (
        <ul>
          {users.map(user => (
            <User key={user.id} name={user.name} />
          ))}
        </ul>
      )}
    </>
  )
}

On gists

App.jsx

App.jsx #

_

On gists

useFetch (custom hook)

React

useFetch.js #

import { useState, useEffect } from 'react';

const useFetch = (url) => {
  const [data, setData] = useState(null);
  const [isPending, setIsPending] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    setTimeout(() => {
      fetch(url)
      .then(res => {
        if (!res.ok) { // error coming back from server
          throw Error('could not fetch the data for that resource');
        } 
        return res.json();
      })
      .then(data => {
        setIsPending(false);
        setData(data);
        setError(null);
      })
      .catch(err => {
        // auto catches network / connection error
        setIsPending(false);
        setError(err.message);
      })
    }, 1000);
  }, [url])

  return { data, isPending, error };
}
 
export default useFetch;

On gists

useState vs useReducer vs mySolution

React

useState.jsx #

import { useState } from "react";

function Cart() {
  const [cart, setCart] = useState({
    items: [],
    total: 0,
    discount: null,
    isLoading: false,
  });

  const addItem = (item) =>
    setCart(prev => ({
      ...prev,
      items: [...prev.items, item],
      total: prev.total + item.price,
    }));

  const removeItem = (id) =>
    setCart(prev => {
      const item = prev.items.find(i => i.id === id);
      return {
        ...prev,
        items: prev.items.filter(i => i.id !== id),
        total: prev.total - (item?.price || 0),
      };
    });

  const applyDiscount = (code) =>
    setCart(prev => ({ ...prev, discount: code }));

  const resetCart = () =>
    setCart({ items: [], total: 0, discount: null, isLoading: false });

  return <div>{JSON.stringify(cart)}</div>;
}

On gists

Css anchor example

CSS

index1.html #

<!DOCTYPE html>
<html lang="cs">
<head>
    <meta charset="UTF-8">
    <title>CSS Anchor Positioning Demo 2026</title>
    <style>
        /* Pomocný styling pro demo */
        body {
            margin: 0;
            font-family: sans-serif;
        }

        section {
            height: 100vh;
            background: orange;
            display: flex;
            align-items: center;
            justify-content: center;
            font-size: 2rem;
            color: white;
        }

        ul {
            padding: 50px;
            background: #eee;
        }

        /* DEFINICE KOTVY (Anchor) */
        li {
            anchor-name: --li; 
            border: 2px solid black;
            min-height: 300px;
            list-style: none;
            display: flex;
            align-items: center;
            justify-content: center;
            background: white;
            /* POZOR: Nesmí zde být position: relative, jinak span kotvu neuvidí */
        }

        /* DEFINICE POZICOVANÉHO PRVKU (Target) */
        span {
            all: unset; /* Vyčištění výchozích stylů */
            display: block;
            background: pink;
            width: fit-content;
            padding: 1rem;
            border-radius: 8px;
            box-shadow: 0 4px 10px rgba(0,0,0,0.2);
            font-weight: bold;


            /* Propojení s kotvou */
            position: fixed;        /* Vztahuje se k viewportu (nutné pro flip)  */
            position-anchor: --li;  /* Jméno kotvy  */
            
            /* Výchozí pozice: NAHOŘE  */
            position-area: top;     
            
            /* AUTOMATICKÉ PŘEKLOPENÍ  */
            /* flip-block automaticky změní 'top' na 'bottom', když nahoře dojde místo */
            position-try-fallbacks: flip-block; 
            
            /* Volitelné: Plynulý přechod pozice (podporováno od Chrome 129+) [1, 2] */
            transition: position-area 0.3s;
        }

        /* Ukázka, jak by vypadalo ruční přepsání přes @position-try, 
           pokud byste nepoužil flip-block:
        
        @position-try --dolu {
            position-area: bottom;
            margin-top: 10px;
        }
        */
    </style>
</head>
<body>

    <section>Scrollujte dolů k růžovému tooltipu...</section>
    
    <ul>
        <li>
            <div>AAA (Kotva)</div>
            <span>BBB (Tooltip - přeskakuje nahoru/dolů)</span>
        </li>
    </ul>
    
    <section>Scrollujte zpět nahoru...</section>

</body>
</html>