/* Adjustable grid */
.grid {
--column-count: 3;
display: grid;
grid-template-columns: repeat(var(--column-count), 1fr);
}
/* modifiers */
.grid-3 { --column-count: 3; }
.grid-4 { --column-count: 4; }
.grid-5 { --column-count: 5; }
/* custom */
.grid { --column-count: 1; }
@media (min-width: 600px) {
.grid { --column-count: 3; }
}
@media (min-width: 960px) {
.grid { --column-count: 4; }
}
/*
-----------------------------------------------------
*/
/* grid auto-columns */
.auto-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(min(200px, 100%), 1fr));
}
/*
-----------------------------------------------------
*/
/* Pushing the footer down */
.main-layout {
min-height: 100vh;
display: grid;
grid-template-rows: rows 1fr rows;
}
/*
-----------------------------------------------------
*/
/* The stack */
.the-stack {
display: grid;
grid-template-areas: "stack";
place-items: center;
}
.the-stack > * {
grid-area: stack;
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<button>CLICK ME</button>
<script>
const channel = new BroadcastChannel('myChannel');
document.getElementsByTagName('button')[0].addEventListener('click', e => {
channel.postMessage('Hello from Tab 1' + new Date().getSeconds);
console.log('CHANNEL 1');
});
channel.onmessage = event => {
console.log('Message received in Tab 2:', event.data);
console.log('CHANNEL 2');
};
</script>
</body>
</html>
<?php
private function deleteDuplicateRecursive(Tables\Navigation $duplicateItem)
{
/*
// OLD
$findDescendants = function(Tables\Navigation $parent, array $stack = [], $level = 1) use (&$findDescendants) {
$children = $this->navigation->getAdjacencyList($parent->domain_id)->getChildren($parent->id);
foreach ($children as $child) {
$stack[$level][] = $child;
$stack = $findDescendants($child, $stack, $level + 1);
}
return $stack;
};
// mazeme deti odspoda nahoru
$descendants = $findDescendants($duplicateItem);
foreach (array_reverse($descendants, true) as $level => $descendantArr) {
foreach ($descendantArr as $descendant) {
$this->deleteDuplicate($descendant);
}
}
*/
// NEW, better, shorter
$children = $this->navigation->getAdjacencyList($duplicateItem->domain_id)->getChildren($duplicateItem->id);
foreach ($children as $child) {
$this->deleteDuplicateRecursive($child);
}
// na zaver i roota
$this->deleteDuplicate($duplicateItem);
}
<!--
https://jsbin.com/heqamezuju/edit?html,css,output
-->
<grid>
<item class="l">
aaa
</item>
<item class="r">
bbb
</item>
</grid>
<style>
grid {
display: grid;
grid-template-columns: 1fr 1fr;
max-width: 500px;
outline: 2px solid gray;
height: 500px;
margin: auto;
}
item {
padding: 10px;
}
.r, .l {
position: relative;
isolation: isolate;
}
.r:after {
background: lightgray;
top: 0;
bottom: 0;
left: 0;
width: 50vw;
position: absolute;
content: "";
z-index: -1;
}
.l:after {
background: lightpink;
top: 0;
bottom: 0;
right: 0;
width: 50vw;
position: absolute;
content: "";
z-index: -1;
}
</style>
<!--
https://lab.rjwebdesign.cz/tailwindcss/advanced/
-->
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link href="./dist/output.css" rel="stylesheet" />
</head>
<body class="p-5">
<h1 class="mb-4 font-bold text-3xl">1) Peer / Group</h1>
<div class="peer group group/second group/third grid place-items-center bg-slate-600 w-20 h-20 mt-10">
<div class="bg-black w-5 h-5 group-hover:bg-red-500"></div>
<div class="bg-black w-5 h-5 group-hover/second:bg-green-500"></div>
<div class="bg-black w-5 h-5 group-hover/third:bg-orange-500"></div>
</div>
<div class="w-20 h-20 bg-orange-400 mt-10 peer-hover:bg-violet-300"></div>
<hr class="my-5" />
<h1 class="mb-4 font-bold text-3xl">2) Animation / Transition</h1>
<div class="w-20 h-20 bg-teal-700 transition-all hover:bg-red-300 delay-300 duration-150"></div>
<hr class="my-5" />
<h1 class="mb-4 font-bold text-3xl">3) Responsivenes / BP (ranges)</h1>
<div class="w-20 h-20 bg-red-500 md:max-lg:bg-green-500"></div>
<hr class="my-5" />
<h1 class="mb-4 font-bold text-3xl">4) Dynamic variants / props / values</h1>
<div class="w-20 h-20 bg-[theme('colors.blue.300')]"></div>
<hr class="my-5" />
<h1 class="mb-4 font-bold text-3xl">5) TW extend / config</h1>
<div class="w-20 h-20 neon-blue"></div>
<hr class="my-5" />
<h1 class="mb-4 font-bold text-3xl">6) TW extend -> COLORS</h1>
<div class="text-primary">Hello how are you?</div>
</body>
</html>
// https://blog.logrocket.com/exploring-advanced-design-patterns-vue-js/
// + MORE ... builder, adapter more usable ...
// BUILDER
// FormBuilder.js
class FormBuilder {
constructor() {
this.formFields = [];
}
addTextField(name, label, placeholder = '') {
this.formFields.push({
type: 'text',
name,
label,
placeholder,
});
return this; // Return the builder instance for method chaining
}
addNumberField(...) {
// Add a number field
}
addSelectField(...) {
// Add a select field
}
build() {
return this.formFields;
}
}
export default FormBuilder;
// Form.vue
import FormBuilder from './helpers/FormBuilder';
export default {
data() {
return {
formFields: [],
formData: {}, // Add a data property to store the form data
};
},
created() {
/*
* Use the FormBuilder class and its methods
* to construct the `formFields` object array
* with different form fields.
*/
this.formFields = new FormBuilder()
.addTextField('name', 'Name')
.addNumberField(...)
.addSelectField(...)
.build();
},
methods: {
handleSubmit() {
// Log the form data when the form is submitted
console.log(this.formData);
},
},
};
// ADAPTER
// UserAdapter.js
class UserAdapter {
constructor(externalUserData) {
this.externalUserData = externalUserData;
}
adapt() {
/*
* Adapt the external user data to match
* the component's expected format.
*
* Considering the structure of the expected
* response, grab the right object array
* (`results`) and its only value.
*
* @link https://randomuser.me/api/
*/
const userData = this.externalUserData.results[0];
return {
name: `${userData.name.first} ${userData.name.last}`,
email: userData.email,
gender: userData.gender,
location: `${userData.location.city}, ${userData.location.country}`,
displayPic: userData.picture.large,
};
}
}
export default UserAdapter;
import UserAdapter from './UserAdapter';
export default {
data() {
return {
userAdapter: null,
user: null
};
},
created() {
/*
* Get user data from an external API
* with a different format.
*/
async created() {
try {
// Make an API call
const response = await fetch('...');
if (!response.ok) {
throw new Error('Failed to fetch external user data');
}
const externalUserData = await response.json();
/*
* Create an adapter to convert external weather data
* to the format expected by the component.
*/
this.userAdapter = new UserAdapter(externalUserData);
// Adapt the data to the component's expected format
this.user = this.userAdapter.adapt();
} catch (error) {
console.error('Error fetching external user data:', error);
this.errorMessage = 'Failed to load user data. Please try again later.';
}
},
}
};
// Composable pattern
// userPreferences.js
import { ref } from 'vue';
const theme = ref('light');
const language = ref('english');
const notifications = ref(true);
// Getter for theme
const getTheme = () => theme.value;
// Setter for theme
const setTheme = (newTheme) => {
theme.value = newTheme;
};
// Getter for language
const getLanguage = () => language.value;
// Setter for language
const setLanguage = (newLanguage) => {
language.value = newLanguage;
};
// Getter for notifications
const getNotificationsEnabled = () => notifications.value;
// Setter for notifications
const setNotificationsEnabled = (enabled) => {
notifications.value = enabled;
};
export {
getTheme,
setTheme,
getLanguage,
setLanguage,
getNotificationsEnabled,
setNotificationsEnabled,
};
// themeSelector.js
import { computed } from 'vue';
import { getTheme, setTheme } from '../utils/userPreferences';
export function useThemeSelection() {
const selectedTheme = computed({
get: () => getTheme(),
set: (newTheme) => setTheme(newTheme),
});
return {
selectedTheme,
};
}
// ThemeSelector.vue
<template>
<div>
<label for="theme-switch">Theme</label>
<select id="theme-switch" @change="handleChange" v-model="selectedTheme">
<option value="light">Light</option>
<option value="dark">Dark</option>
</select>
</div>
</template>
<script>
import { defineComponent } from 'vue';
import { useThemeSelection } from '../composables/themeSelector';
export default defineComponent({
setup() {
const { selectedTheme } = useThemeSelection();
const handleChange = (event) => {
const newTheme = event.target.value;
selectedTheme.value = newTheme; // Set the new theme
};
return {
selectedTheme,
handleChange,
};
},
});
</script>
// AnyOther.vue
<template>
<div class="settings">
<div class="settings-row">
<ThemeSelector />
<div :class="selectedTheme">
Here's how the {{ selectedTheme }} theme looks like.
</div>
</div>
</div>
</template>
<script>
import { defineComponent } from 'vue';
import { useThemeSelection } from './composables/themeSelector';
import ThemeSelector from './components/ThemeSelector';
export default defineComponent({
setup() {
const { selectedTheme } = useThemeSelection();
return {
selectedTheme,
};
},
components: {
ThemeSelector
},
});
</script>
<style>
.light {
background-color: #fff;
color: #000;
}
.dark {
background-color: #000;
color: #fff;
}
</style>