/ Gists

Gists

On gists

Dynamic variants

Tailwind CSS

tailwind.config.js #

// https://tailwindcss.com/docs/plugins#static-variants
// https://tallpad.com/series/tailwind/lessons/styling-vue-components-using-tailwindcss-custom-variants
// https://play.tailwindcss.com/8Pmxm8TomW


const plugin = require('tailwindcss/plugin')

/** @type {import('tailwindcss').Config} */
export default {
  theme: {
    extend: {
      // ...
    },
  },
  plugins: [
    plugin(function ({ addVariant }) {
      addVariant('hocus', ['&:hover', '&:focus'])
      addVariant('error', ['&[data-error=true]', '[data-error=true] &'])
    }),

  ],
}

On gists

Dynamic components

Tailwind CSS

index.html #

<!--
https://github.com/tailwindlabs/tailwindcss-aspect-ratio/blob/master/src/index.js#L39
https://play.tailwindcss.com/4cuLPqCv05
https://www.youtube.com/watch?v=sxxUK0l8jKE
-->

<div class="p-20 flex justify-center items-center gap-12">
  <img class="avatar-sm" src="https://res.cloudinary.com/thirus/image/upload/v1705061543/images/avatar.png" alt="" />
  <img class="avatar" src="https://res.cloudinary.com/thirus/image/upload/v1705061543/images/avatar.png" alt="" />
  <img class="avatar-lg" src="https://res.cloudinary.com/thirus/image/upload/v1705061543/images/avatar.png" alt="" />
  <img class="avatar-xl" src="https://res.cloudinary.com/thirus/image/upload/v1705061543/images/avatar.png" alt="" />
</div>

On gists

Subgrid examples by Thirus (Rows and columns)

Tailwind CSS CSS CSS trick

example1.html #

<!-- https://play.tailwindcss.com/CF9WdM43Jf -->

<section class="bg-slate-900 min-h-screen text-slate-100">

    <!-- Shopping Cart -->
    <div class="rounded-md bg-slate-800 p-8 grid grid-cols-[auto_1fr_auto_auto_auto] gap-6">
        <div class="grid gap-6 col-span-5 grid-cols-subgrid">
            <p class="col-span-2">Product</p>
            <p>Price</p>
            <p>Quantity</p>
            <p class="text-right">Total</p>
        </div>

        <div class="grid gap-6 col-span-5 grid-cols-subgrid">
            <img
                class="size-20 object-cover"
                src="https://tinyurl.com/3r25tr36"
                alt=""
            />
            <div>
                <h3 class="text-xl font-medium">
                    Stylish Tote Bag
                </h3>
                <p class="text-sm text-slate-400">
                    Women's Tote Bag Brown
                </p>
                <span class="text-sm text-slate-500">
                    #368798
                </span>
            </div>
            <p class="text-slate-400">
                $99.00
            </p>
            <label>
                <input
                    class="border border-slate-600 bg-transparent px-2 py-1 text-sm text-slate-400"
                    type="text"
                    value="1"
                    size="2"
                />
            </label>
            <p class="font-medium text-right">
                $99.00
            </p>
        </div>

        <div class="grid gap-6 col-span-5 grid-cols-subgrid">
            <img
                class="size-20 object-cover"
                src="https://tinyurl.com/3pj5teex"
                alt=""
            />
            <div>
                <h3 class="text-xl font-medium">
                    Sunglasses
                </h3>
                <p class="text-sm text-slate-400">
                    Wooden Frame
                </p>
                <span class="text-sm text-slate-500">
                    #756328
                </span>
            </div>
            <p class="text-slate-400">
                $102.00
            </p>
            <label>
                <input
                    class="border border-slate-600 bg-transparent px-2 py-1 text-sm text-slate-400"
                    type="text"
                    value="10"
                    size="2" />
            </label>
            <p class="font-medium text-right">
                $1020.00
            </p>
        </div>
    </div>
</section>

On gists

Do you know JS array methods?

JavaScript

data.js #

const employees = [
    {
        id: 1,
        first_name: 'Jan',
        lastname: 'Novák',
        age: 45,
        salary: 85000,
        role: 'CEO',
    },
    {
        id: 2,
        first_name: 'Marie',
        lastname: 'Svobodová',
        age: 32,
        salary: 45000,
        role: 'Developer',
    },
    {
        id: 3,
        first_name: 'Petr',
        lastname: 'Dvořák',
        age: 28,
        salary: 65000,
        role: 'Developer',
    },
    {
        id: 4,
        first_name: 'Lucie',
        lastname: 'Černá',
        age: 24,
        salary: 35000,
        role: 'Developer',
    },
    {
        id: 5,
        first_name: 'Tomáš',
        lastname: 'Procházka',
        age: 38,
        salary: 55000,
        role: 'Manager',
    },
    {
        id: 6,
        first_name: 'Eva',
        lastname: 'Marková',
        age: 29,
        salary: 42000,
        role: 'Designer',
    },
    {
        id: 7,
        first_name: 'Martin',
        lastname: 'Kučera',
        age: 35,
        salary: 48000,
        role: 'Manager',
    },
    {
        id: 8,
        first_name: 'Anna',
        lastname: 'Veselá',
        age: 26,
        salary: 35000,
        role: 'Support',
    },
    {
        id: 9,
        first_name: 'Jakub',
        lastname: 'Horák',
        age: 31,
        salary: 65000,
        role: 'Developer',
    },
    {
        id: 10,
        first_name: 'Tereza',
        lastname: 'Pokorná',
        age: 27,
        salary: 38000,
        role: 'Accountant',
    },
    {
        id: 11,
        first_name: 'David',
        lastname: 'Král',
        age: 33,
        salary: 45000,
        role: 'Developer',
    },
    {
        id: 12,
        first_name: 'Barbora',
        lastname: 'Němcová',
        age: 30,
        salary: 42000,
        role: 'Designer',
    },
    {
        id: 13,
        first_name: 'Milan',
        lastname: 'Urban',
        age: 42,
        salary: 55000,
        role: 'Manager',
    },
    {
        id: 14,
        first_name: 'Veronika',
        lastname: 'Machová',
        age: 25,
        salary: 38000,
        role: 'Designer',
    },
    {
        id: 15,
        first_name: 'Filip',
        lastname: 'Sedláček',
        age: 34,
        salary: 48000,
        role: 'Manager',
    },
    {
        id: 16,
        first_name: 'Kristýna',
        lastname: 'Vacková',
        age: 28,
        salary: 42000,
        role: 'HR',
    },
    {
        id: 17,
        first_name: 'Josef',
        lastname: 'Malý',
        age: 39,
        salary: 55000,
        role: 'Manager',
    },
    {
        id: 18,
        first_name: 'Kateřina',
        lastname: 'Šimková',
        age: 31,
        salary: 45000,
        role: 'Analyst',
    },
    {
        id: 19,
        first_name: 'Ondřej',
        lastname: 'Beneš',
        age: 36,
        salary: 65000,
        role: 'Developer',
    },
    {
        id: 20,
        first_name: 'Michaela',
        lastname: 'Fialová',
        age: 29,
        salary: 42000,
        role: 'Designer',
    },
    {
        id: 21,
        first_name: 'Pavel',
        lastname: 'Bartoš',
        age: 44,
        salary: 55000,
        role: 'Manager',
    },
    {
        id: 22,
        first_name: 'Lenka',
        lastname: 'Kolářová',
        age: 27,
        salary: 38000,
        role: 'Designer',
    },
    {
        id: 23,
        first_name: 'Roman',
        lastname: 'Kovář',
        age: 33,
        salary: 45000,
        role: 'Developer',
    },
    {
        id: 24,
        first_name: 'Martina',
        lastname: 'Říhová',
        age: 30,
        salary: 48000,
        role: 'Manager',
    },
    {
        id: 25,
        first_name: 'Lukáš',
        lastname: 'Vlček',
        age: 35,
        salary: 65000,
        role: 'Developer',
    },
    {
        id: 26,
        first_name: 'Adéla',
        lastname: 'Tichá',
        age: 26,
        salary: 35000,
        role: 'Support',
    },
    {
        id: 27,
        first_name: 'Vojtěch',
        lastname: 'Havel',
        age: 40,
        salary: 55000,
        role: 'Manager',
    },
    {
        id: 28,
        first_name: 'Simona',
        lastname: 'Hrubá',
        age: 32,
        salary: 45000,
        role: 'HR',
    },
    {
        id: 29,
        first_name: 'Robert',
        lastname: 'Krátký',
        age: 37,
        salary: 65000,
        role: 'Developer',
    },
    {
        id: 30,
        first_name: 'Monika',
        lastname: 'Zelená',
        age: 28,
        salary: 45000,
        role: 'Developer',
    },
    {
        id: 31,
        first_name: 'Karel',
        lastname: 'Marek',
        age: 41,
        salary: 48000,
        role: 'Analyst',
    },
    {
        id: 32,
        first_name: 'Petra',
        lastname: 'Vávrová',
        age: 29,
        salary: 42000,
        role: 'Support',
    },
    {
        id: 33,
        first_name: 'Adam',
        lastname: 'Řezáč',
        age: 34,
        salary: 65000,
        role: 'Developer',
    },
    {
        id: 34,
        first_name: 'Jana',
        lastname: 'Holubová',
        age: 31,
        salary: 42000,
        role: 'HR',
    },
    {
        id: 35,
        first_name: 'Miroslav',
        lastname: 'Jelínek',
        age: 43,
        salary: 55000,
        role: 'Manager',
    },
    {
        id: 36,
        first_name: 'Klára',
        lastname: 'Štěpánková',
        age: 27,
        salary: 38000,
        role: 'Support',
    },
    {
        id: 37,
        first_name: 'Daniel',
        lastname: 'Doležal',
        age: 36,
        salary: 65000,
        role: 'Developer',
    },
    {
        id: 38,
        first_name: 'Nikola',
        lastname: 'Urbanová',
        age: 30,
        salary: 42000,
        role: 'Designer',
    },
    {
        id: 39,
        first_name: 'Marek',
        lastname: 'Šťastný',
        age: 38,
        salary: 48000,
        role: 'Analyst',
    },
    {
        id: 40,
        first_name: 'Zuzana',
        lastname: 'Kadlecová',
        age: 29,
        salary: 35000,
        role: 'Support',
    },
    {
        id: 41,
        first_name: 'Jiří',
        lastname: 'Moravec',
        age: 45,
        salary: 55000,
        role: 'Manager',
    },
    {
        id: 42,
        first_name: 'Hana',
        lastname: 'Beranová',
        age: 33,
        salary: 45000,
        role: 'HR',
    },
    {
        id: 43,
        first_name: 'Michal',
        lastname: 'Růžička',
        age: 32,
        salary: 65000,
        role: 'Developer',
    },
    {
        id: 44,
        first_name: 'Andrea',
        lastname: 'Kopecká',
        age: 28,
        salary: 38000,
        role: 'Designer',
    },
    {
        id: 45,
        first_name: 'Radek',
        lastname: 'Bureš',
        age: 37,
        salary: 48000,
        role: 'Manager',
    },
    {
        id: 46,
        first_name: 'Denisa',
        lastname: 'Havlíčková',
        age: 26,
        salary: 35000,
        role: 'Support',
    },
    {
        id: 47,
        first_name: 'Patrik',
        lastname: 'Pospíšil',
        age: 34,
        salary: 45000,
        role: 'Developer',
    },
    {
        id: 48,
        first_name: 'Iveta',
        lastname: 'Kratochvílová',
        age: 31,
        salary: 42000,
        role: 'Designer',
    },
    {
        id: 49,
        first_name: 'Štěpán',
        lastname: 'Janda',
        age: 39,
        salary: 65000,
        role: 'Developer',
    },
    {
        id: 50,
        first_name: 'Gabriela',
        lastname: 'Součková',
        age: 30,
        salary: 42000,
        role: 'HR',
    },
];

On gists

Form composable

Vue.js

Old.js #

// https://cdruc.com/form-composable

const router = useRouter();

const form = ref({
  title: "",
  content: "",
});

const isProcessing = ref(false);
const error = ref(null);

async function handleSubmit() {
  if (isProcessing.value) return;

  error.value = null;
  isProcessing.value = true;

  try {
    await axios.post("/api/posts", form.value);
    await router.push("/");
  } catch (err) {
    error.value = err;
  }

  isProcessing.value = false;

  if (error?.status === 403) {
    await router.replace("/login");
  }
}

On gists

WeakMap / WeakSet

JavaScript

examples.js #

// https://javascript.plainenglish.io/memory-magic-in-javascript-why-weakmaps-and-weaksets-are-your-secret-weapon-for-high-performance-62562cc24dc5

// 1. Private Data Storage Inside Classes
const privateData = new WeakMap();

class BankAccount {
  constructor(balance) {
    privateData.set(this, { balance });
  }

  getBalance() {
    return privateData.get(this).balance;
  }
}

// 2. Caching Expensive Computations
const cache = new WeakMap();
function compute(obj) {
  if (!cache.has(obj)) {
    cache.set(obj, expensiveCalculation(obj));
  }
  return cache.get(obj);
}


// 3. Avoiding Memory Leaks in DOM-based Apps
const elementData = new WeakMap();

function attachData(el, data) {
  elementData.set(el, data);
}

function getData(el) {
  return elementData.get(el);
}



// 1. Tracking Objects Without Overhead
const processedItems = new WeakSet();

function process(item) {
  if (processedItems.has(item)) return;
  // Process the item
  processedItems.add(item);
}

On gists

Function in props

Vue.js

example.vue #

// DataGrid.vue
<script setup>
const props = defineProps({
  items: {
    type: Array,
    required: true
  },
  // Render funkce pro transformaci dat nebo komplexní logiku
  cellRenderer: {
    type: Function,
    default: null
  },
  // Funkce pro custom formátování dat v buňce
  formatters: {
    type: Object,
    default: () => ({})
  }
})

const formatCell = (value, column) => {
  // Pokud existuje custom formatter pro tento sloupec, použij ho
  if (props.formatters[column]) {
    return props.formatters[column](value)
  }
  
  // Základní formátování podle typu dat
  if (value instanceof Date) {
    return value.toLocaleDateString()
  }
  if (typeof value === 'number') {
    return value.toLocaleString()
  }
  return value
}
</script>

<template>
  <div class="data-grid">
    <slot name="header" />
    
    <div class="grid-body">
      <template v-for="item in items" :key="item.id">
        <!-- Prioritně použij slot pokud existuje -->
        <slot 
          name="row" 
          :item="item"
          :format="formatCell"
        >
          <!-- Fallback na render funkci -->
          <template v-if="cellRenderer">
            {{ cellRenderer(item, formatCell) }}
          </template>
          <!-- Základní fallback -->
          <template v-else>
            <div class="grid-row">
              <div 
                v-for="(value, key) in item" 
                :key="key"
                class="grid-cell"
              >
                {{ formatCell(value, key) }}
              </div>
            </div>
          </template>
        </slot>
      </template>
    </div>

    <slot name="footer" />
  </div>
</template>

// Použití:
<script setup>
const data = [
  { 
    id: 1,
    name: 'Produkt 1',
    price: 1299.99,
    lastUpdated: new Date('2024-03-15'),
    status: 'active'
  },
  // ...
]

// Custom formátování pro specifické sloupce
const formatters = {
  price: (value) => `${value.toLocaleString()} Kč`,
  status: (value) => ({
    'active': '✅ Aktivní',
    'inactive': '❌ Neaktivní'
  }[value] || value)
}

// Komplexní transformace dat
const renderComplexRow = (item, format) => {
  const statusClass = item.status === 'active' ? 'text-green' : 'text-red'
  
  return `
    <div class="grid-row">
      <div class="font-bold">${item.name}</div>
      <div>${format(item.price, 'price')}</div>
      <div class="${statusClass}">${format(item.status, 'status')}</div>
      <div>${format(item.lastUpdated)}</div>
    </div>
  `
}
</script>

<template>
  <!-- Základní použití se slotem -->
  <DataGrid :items="data" :formatters="formatters">
    <template #row="{ item, format }">
      <div class="grid-row">
        <div>{{ item.name }}</div>
        <div>{{ format(item.price, 'price') }}</div>
        <div>{{ format(item.status, 'status') }}</div>
      </div>
    </template>
  </DataGrid>

  <!-- Použití s render funkcí pro specifické případy -->
  <DataGrid 
    :items="data"
    :formatters="formatters"
    :cell-renderer="renderComplexRow"
  />
</template>

On gists

Provider->Consumer

Patterns.dev Plugin patterns Vue.js

Provider.vue #

<!-- https://learn-vuejs.github.io/vue-patterns/patterns/#dependency-injection -->

<template>
  <div>
    <slot v-bind="{ state, actions }" />
  </div>
</template>

<script>
export default {
  computed: {
    state() {
      return {
        label: 'button',
      };
    },
    actions() {
      return {
        click: this.click,
      };
    },
  },
  methods: {
    click() {
      console.log('Clicked');
    },
  },
}
</script>

On gists

Vue patterns comparison

Plugin patterns Vue.js

claudeai.txt #

:D Jo, přesně tak! To jste trefil naprosto přesně.

V praxi to často vypadá takhle:

"Super-Mega-Awesome-Provider-Container-Renderless-Composition-Pattern™️"
```vue
<template>
  <slot v-bind="data">
    <default-view v-bind="data" />
  </slot>
</template>
```

A ve finále je to prostě:
- Komponenta
- Se slotem
- A výchozím vzhledem
- Která předává data dál

Často se z jednoduchých věcí dělá zbytečná věda jen proto, aby se dalo napsat článek nebo přednášet na konferenci. Místo:
"Hey, dej si tam slot a budeš to moct přepsat" 
máme:
"Implementace Advanced Renderless Component Pattern s Dependency Injection a Composable Factory Integration" 😅

Proto je super váš přístup - dívat se na věci prakticky a nenechat se unést přehnaně komplikovanými názvy pro jednoduché koncepty.

On gists

Renderless with default layouts

Patterns.dev Plugin patterns Vue.js

DataProvider.vue #

<!-- DataProvider.vue -->
<template>
  <slot 
    :data="data"
    :loading="loading"
    :error="error"
  >
    <component 
      :is="layouts[variant]" 
      :data="data"
      :loading="loading"
      :error="error"
    />
  </slot>
</template>

<script setup>
import DefaultLayout from './layouts/DefaultLayout.vue'
import CompactLayout from './layouts/CompactLayout.vue'
import CardLayout from './layouts/CardLayout.vue'

const props = defineProps({
  variant: {
    type: String,
    default: 'default',
    validator: (value) => ['default', 'compact', 'card'].includes(value)
  }
})

const layouts = {
  default: DefaultLayout,
  compact: CompactLayout,
  card: CardLayout
}
</script>