initial commit
This commit is contained in:
42
README.md
Normal file
42
README.md
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
# Nuxt 3 Minimal Starter
|
||||||
|
|
||||||
|
Look at the [Nuxt 3 documentation](https://nuxt.com/docs/getting-started/introduction) to learn more.
|
||||||
|
|
||||||
|
## Setup
|
||||||
|
|
||||||
|
Make sure to install the dependencies:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# yarn
|
||||||
|
yarn install
|
||||||
|
|
||||||
|
# npm
|
||||||
|
npm install
|
||||||
|
|
||||||
|
# pnpm
|
||||||
|
pnpm install
|
||||||
|
```
|
||||||
|
|
||||||
|
## Development Server
|
||||||
|
|
||||||
|
Start the development server on `http://localhost:3000`
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm run dev
|
||||||
|
```
|
||||||
|
|
||||||
|
## Production
|
||||||
|
|
||||||
|
Build the application for production:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm run build
|
||||||
|
```
|
||||||
|
|
||||||
|
Locally preview production build:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm run preview
|
||||||
|
```
|
||||||
|
|
||||||
|
Check out the [deployment documentation](https://nuxt.com/docs/getting-started/deployment) for more information.
|
||||||
125
assets/styles.css
Normal file
125
assets/styles.css
Normal file
@@ -0,0 +1,125 @@
|
|||||||
|
body {
|
||||||
|
font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont,
|
||||||
|
"Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif,
|
||||||
|
"Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
|
||||||
|
color: white;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
margin: 12px;
|
||||||
|
background: #070809;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vl-button,
|
||||||
|
.vl-toggle-button {
|
||||||
|
border: solid 1px #1d1e1f;
|
||||||
|
background: #1a1b1c;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vl-button,
|
||||||
|
.vl-toggle-button,
|
||||||
|
.vl-dropdown button {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
color: #efefef;
|
||||||
|
padding-left: 8px;
|
||||||
|
padding-right: 8px;
|
||||||
|
padding-top: 6px;
|
||||||
|
padding-bottom: 6px;
|
||||||
|
border-radius: 8px;
|
||||||
|
transition-property: background-color, border-color;
|
||||||
|
transition-duration: 250ms;
|
||||||
|
transition-timing-function: ease-in;
|
||||||
|
cursor: pointer;
|
||||||
|
min-width: 65px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vl-button svg {
|
||||||
|
margin-left: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vl-toggle-group {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
border: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vl-toggle-button {
|
||||||
|
min-width: fit-content;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vl-toggle-button svg {
|
||||||
|
margin-left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vl-button:hover,
|
||||||
|
button:hover {
|
||||||
|
background: #1d1e1f;
|
||||||
|
border-color: #202122;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vl-button:focus, button:focus {
|
||||||
|
outline: 2px solid #4e367e;
|
||||||
|
background: #1d1e1f;
|
||||||
|
border-color: #202122;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vl-button:active,
|
||||||
|
button:active {
|
||||||
|
background: #191a1b;
|
||||||
|
border-color: #1c1d1e;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vl-dropdown {
|
||||||
|
box-shadow: 0 0 12px #00000071;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vl-dropdown-enter-active,
|
||||||
|
.vl-dropdown-leave-active {
|
||||||
|
transition: all 300ms ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vl-dropdown-enter-from,
|
||||||
|
.vl-dropdown-leave-to {
|
||||||
|
transform: translateY(-8px) scale(0.85);
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vl-toggle-button {
|
||||||
|
color: #bcbcbc;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vl-toggle-group button {
|
||||||
|
border-radius: 0px;
|
||||||
|
border-left: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vl-toggle-group button:first-child {
|
||||||
|
border-radius: 8px 0px 0px 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vl-toggle-group button:last-child {
|
||||||
|
border-radius: 0px 8px 8px 0px;
|
||||||
|
border-left: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
.vl-select-left:active, .vl-select-middle:active, .vl-select-right:active, .vl-select-left:hover, .vl-select-middle:hover, .vl-select-right:hover {
|
||||||
|
border-color: #1d1e1f;
|
||||||
|
}*/
|
||||||
|
|
||||||
|
.vl-toggle-button[data-state="checked"],
|
||||||
|
.vl-toggle-button[data-state="on"] {
|
||||||
|
background: #1f2021;
|
||||||
|
color: #efefef;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vl-toggle-button:focus {
|
||||||
|
position: relative;
|
||||||
|
z-index: 1;
|
||||||
|
box-shadow: #4e367e 0px 0px 0px 2px
|
||||||
|
}
|
||||||
24
components/vlAccordion/content.vue
Normal file
24
components/vlAccordion/content.vue
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
<script setup>
|
||||||
|
const item = inject('accordionItem');
|
||||||
|
const contentHeight = ref(0);
|
||||||
|
const content = ref(null);
|
||||||
|
|
||||||
|
watch(item.hidden, () => {
|
||||||
|
if (!item.hidden.value) {
|
||||||
|
setTimeout(() => {
|
||||||
|
contentHeight.value = content.value.offsetHeight;
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div :id="`vueless-${item.index}`" role="region" :aria-labelledby="`vueless-${item.index}`" class="vl-accordion-content"
|
||||||
|
:style="`--vueless-accordion-content-height: ${contentHeight}px`"
|
||||||
|
:data-state="(item.isOpen.value) ? 'open' : 'closed'"
|
||||||
|
@animationend="(!item.isOpen.value) ? item.hidden.value = true : ''" :hidden="item.hidden.value">
|
||||||
|
<div ref="content">
|
||||||
|
<slot />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
194
components/vlAccordion/index.vue
Normal file
194
components/vlAccordion/index.vue
Normal file
@@ -0,0 +1,194 @@
|
|||||||
|
<script setup>
|
||||||
|
const props = defineProps({
|
||||||
|
type: {
|
||||||
|
type: String,
|
||||||
|
default: "multiple"
|
||||||
|
},
|
||||||
|
defaultValue: {
|
||||||
|
type: String
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const accordion = ref(null);
|
||||||
|
|
||||||
|
const accordionItems = ref([])
|
||||||
|
|
||||||
|
function toggleAccordion(index) {
|
||||||
|
const item = accordionItems.value[index];
|
||||||
|
if (props.type === "single") {
|
||||||
|
// close everything but the one we just opened
|
||||||
|
accordionItems.value.forEach((item, i) => {
|
||||||
|
if (i === index) return;
|
||||||
|
item.isOpen = false;
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if (item.hidden) {
|
||||||
|
item.hidden = false;
|
||||||
|
}
|
||||||
|
item.isOpen = !item.isOpen;
|
||||||
|
}
|
||||||
|
|
||||||
|
const registerAccordionItem = (value) => {
|
||||||
|
const item = { isOpen: ref(false), hidden: ref(true), value }
|
||||||
|
accordionItems.value.push(item);
|
||||||
|
|
||||||
|
return { index: accordionItems.value.indexOf(item), isOpen: item.isOpen, hidden: item.hidden, value };
|
||||||
|
};
|
||||||
|
|
||||||
|
const unregisterAccordionItem = (index) => {
|
||||||
|
accordionItems.value.splice(index, 1);
|
||||||
|
};
|
||||||
|
|
||||||
|
provide('accordion', { registerAccordionItem, unregisterAccordionItem, toggleAccordion })
|
||||||
|
|
||||||
|
function keydown(event) {
|
||||||
|
const headers = Array.from(accordion.value.querySelectorAll(".vl-accordion-header"));
|
||||||
|
|
||||||
|
if (event.key === "ArrowUp") {
|
||||||
|
event.preventDefault();
|
||||||
|
const focusedElement = document.activeElement;
|
||||||
|
const currentIndex = headers.indexOf(focusedElement.parentElement);
|
||||||
|
const nextIndex = currentIndex > 0 ? currentIndex - 1 : headers.length - 1;
|
||||||
|
const nextButton = headers[nextIndex].querySelector("button");
|
||||||
|
nextButton.focus();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event.key === "ArrowDown") {
|
||||||
|
event.preventDefault();
|
||||||
|
const focusedElement = document.activeElement;
|
||||||
|
const currentIndex = headers.indexOf(focusedElement.parentElement);
|
||||||
|
const nextIndex = currentIndex < headers.length - 1 ? currentIndex + 1 : 0;
|
||||||
|
const nextButton = headers[nextIndex].querySelector("button");
|
||||||
|
nextButton.focus();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event.key === "End") {
|
||||||
|
event.preventDefault();
|
||||||
|
return headers[headers.length - 1].querySelector("button").focus();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event.key === "Home") {
|
||||||
|
event.preventDefault();
|
||||||
|
return headers[0].querySelector("button").focus();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
if (props.defaultValue) {
|
||||||
|
const item = accordionItems.value.filter(item => item.value === props.defaultValue)[0];
|
||||||
|
item.isOpen = true;
|
||||||
|
item.hidden = false;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="vl-accordion" ref="accordion" @keydown="keydown($event)">
|
||||||
|
<slot />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.vl-accordion {
|
||||||
|
border-radius: 6px;
|
||||||
|
background-color: #1D1E1F;
|
||||||
|
box-shadow: 0 0 16px 0 #07070738;
|
||||||
|
width: 70%;
|
||||||
|
user-select: none;
|
||||||
|
cursor: default;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vl-accordion-content {
|
||||||
|
width: var(--vueless-accordion-content-width);
|
||||||
|
background-color: #0B0C0D;
|
||||||
|
overflow: hidden;
|
||||||
|
transform-origin: top center;
|
||||||
|
height: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vl-accordion-item:focus-within {
|
||||||
|
position: relative;
|
||||||
|
z-index: 1;
|
||||||
|
box-shadow: #4e367e 0px 0px 0px 2px
|
||||||
|
}
|
||||||
|
|
||||||
|
.vl-accordion-content div {
|
||||||
|
padding: 15px 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vl-accordion-content[data-state="closed"] {
|
||||||
|
animation: 300ms cubic-bezier(0.87, 0, 0.13, 1) 0s 1 normal forwards running closeAccordion;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vl-accordion-content[data-state="open"] {
|
||||||
|
animation: 300ms cubic-bezier(0.87, 0, 0.13, 1) 0s 1 normal forwards running openAccordion;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vl-accordion-item h3 button svg {
|
||||||
|
transition: transform 300ms ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vl-accordion-item[data-state="open"] h3 button svg {
|
||||||
|
transform: rotate(180deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.vl-accordion-item {
|
||||||
|
background-color: #161718;
|
||||||
|
height: 100%;
|
||||||
|
margin-top: 1px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vl-accordion-item:first-child {
|
||||||
|
margin-top: 0px;
|
||||||
|
border-radius: 6px 6px 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vl-accordion-item:last-child {
|
||||||
|
border-radius: 0 0 6px 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vl-accordion-header:hover {
|
||||||
|
background-color: #131415;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vl-accordion-header button {
|
||||||
|
all: unset;
|
||||||
|
background-color: transparent;
|
||||||
|
height: 45px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
line-height: 1;
|
||||||
|
padding: 0px 20px;
|
||||||
|
flex: 1 1 0%;
|
||||||
|
box-shadow: #1D1E1F 0px 1px 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vl-accordion-header {
|
||||||
|
all: unset;
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes closeAccordion {
|
||||||
|
0% {
|
||||||
|
height: var(--vueless-accordion-content-height);
|
||||||
|
}
|
||||||
|
|
||||||
|
100% {
|
||||||
|
height: 0px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes openAccordion {
|
||||||
|
0% {
|
||||||
|
height: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
100% {
|
||||||
|
height: var(--vueless-accordion-content-height);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
19
components/vlAccordion/item.vue
Normal file
19
components/vlAccordion/item.vue
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
<script setup>
|
||||||
|
const props = defineProps({
|
||||||
|
value: {
|
||||||
|
type: String,
|
||||||
|
default: "",
|
||||||
|
}
|
||||||
|
})
|
||||||
|
const accordion = inject('accordion')
|
||||||
|
|
||||||
|
const item = accordion.registerAccordionItem(props.value);
|
||||||
|
|
||||||
|
provide('accordionItem', item);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="vl-accordion-item" :data-state="(item.isOpen.value) ? 'open' : 'closed'">
|
||||||
|
<slot />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
13
components/vlAccordion/trigger.vue
Normal file
13
components/vlAccordion/trigger.vue
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
<script setup>
|
||||||
|
const item = inject('accordionItem');
|
||||||
|
const { toggleAccordion } = inject('accordion');
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<h3 class="vl-accordion-header">
|
||||||
|
<button :id="`vueless-${item.index}`" :aria-controls="`vueless-${item.index}`" :aria-expanded="item.isOpen.value"
|
||||||
|
@click="toggleAccordion(item.index)">
|
||||||
|
<slot />
|
||||||
|
</button>
|
||||||
|
</h3>
|
||||||
|
</template>
|
||||||
151
components/vlDropdown.vue
Normal file
151
components/vlDropdown.vue
Normal file
@@ -0,0 +1,151 @@
|
|||||||
|
<script setup>
|
||||||
|
const hex = ref((Math.random() * 0xfffff * 1000000).toString(16).slice(0, 6).toString());
|
||||||
|
const dropdownOpen = ref(false);
|
||||||
|
const dropdownButton = ref(null);
|
||||||
|
const dropdown = ref(null);
|
||||||
|
|
||||||
|
function dropdownButtonKeypress(event) {
|
||||||
|
if (event.key !== "Enter" && event.key !== " " && event.key !== "ArrowUp" && event.key !== "ArrowDown" || dropdownOpen.value === true) return;
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
|
if (event.key === "ArrowUp") {
|
||||||
|
dropdownOpen.value = true;
|
||||||
|
setTimeout(() => {
|
||||||
|
dropdown.value.children[dropdown.value.children.length - 1].focus();
|
||||||
|
})
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event.key === "ArrowDown") {
|
||||||
|
dropdownOpen.value = true;
|
||||||
|
setTimeout(() => {
|
||||||
|
dropdown.value.children[1].focus();
|
||||||
|
})
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event.key === "Enter" || event.key === " ") {
|
||||||
|
dropdownOpen.value = !dropdownOpen.value;
|
||||||
|
setTimeout(() => {
|
||||||
|
dropdown.value.children[1].focus();
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function dropdownKeypress(event) {
|
||||||
|
if (event.key === "ArrowUp") {
|
||||||
|
event.preventDefault();
|
||||||
|
const focusedElement = document.activeElement;
|
||||||
|
const elements = Array.from(dropdown.value.children).filter(
|
||||||
|
(el) => el.tabIndex === 0
|
||||||
|
);
|
||||||
|
const currentIndex = elements.indexOf(focusedElement);
|
||||||
|
const nextIndex = currentIndex > 0 ? currentIndex - 1 : elements.length - 1;
|
||||||
|
elements[nextIndex].focus();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event.key === "ArrowDown") {
|
||||||
|
event.preventDefault();
|
||||||
|
const focusedElement = document.activeElement;
|
||||||
|
const elements = Array.from(dropdown.value.children).filter(
|
||||||
|
(el) => el.tabIndex === 0
|
||||||
|
);
|
||||||
|
const currentIndex = elements.indexOf(focusedElement);
|
||||||
|
const nextIndex = currentIndex < elements.length - 1 ? currentIndex + 1 : 0;
|
||||||
|
elements[nextIndex].focus();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event.key === "Escape" || event.key === "Esc") {
|
||||||
|
event.preventDefault();
|
||||||
|
dropdownButton.value.focus();
|
||||||
|
dropdownOpen.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="vl-dropdown-button">
|
||||||
|
<button :id="`menubutton-${hex}`" :aria-controls="`menu-${hex}`" class="vl-button"
|
||||||
|
@click.exact="dropdownOpen = !dropdownOpen;" @keydown="dropdownButtonKeypress($event)" ref="dropdownButton"
|
||||||
|
aria-haspopup="menu" :aria-expanded="dropdownOpen" :data-state="(dropdownOpen) ? 'open' : 'closed'">
|
||||||
|
Dropdown
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24">
|
||||||
|
<path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||||
|
d="m6 9l6 6l6-6" />
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
<Transition name="vl-dropdown">
|
||||||
|
<div class="vl-dropdown" :id="`menu-${hex}`" tabindex="-1" :aria-labelledby="`menubutton-${hex}`"
|
||||||
|
:data-state="(dropdownOpen) ? 'open' : 'closed'" :key="dropdownOpen" :hidden="!dropdownOpen" role="menu"
|
||||||
|
@keydown="dropdownKeypress($event)" :aria-hidden="!dropdownOpen" ref="dropdown">
|
||||||
|
<span class="vl-diamond"><svg style="display:block" width="10" height="5" viewBox="0 0 30 10"
|
||||||
|
preserveAspectRatio="none">
|
||||||
|
<polygon points="0,0 30,0 15,10" style=""></polygon>
|
||||||
|
</svg></span>
|
||||||
|
<slot />
|
||||||
|
</div>
|
||||||
|
</Transition>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.vl-dropdown-button .vl-button[data-state="open"] svg {
|
||||||
|
transform: rotate(180deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.vl-dropdown-button .vl-button svg {
|
||||||
|
transition: transform 300ms ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vl-dropdown-button {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vl-diamond {
|
||||||
|
position: absolute;
|
||||||
|
top: 0px;
|
||||||
|
transform-origin: center 0px 0px;
|
||||||
|
transform: rotate(180deg) translateX(50%);
|
||||||
|
left: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vl-diamond svg {
|
||||||
|
fill: #070809;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vl-dropdown[data-state="open"] {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vl-dropdown {
|
||||||
|
position: absolute;
|
||||||
|
padding-top: 4px;
|
||||||
|
padding-bottom: 4px;
|
||||||
|
padding-right: 4px;
|
||||||
|
padding-left: 4px;
|
||||||
|
margin-top: 8px;
|
||||||
|
left: -25%;
|
||||||
|
width: 150%;
|
||||||
|
background: #070809;
|
||||||
|
border-radius: 6px;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 2px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vl-dropdown button {
|
||||||
|
width: 100%;
|
||||||
|
background: unset;
|
||||||
|
border: unset;
|
||||||
|
justify-content: start;
|
||||||
|
border-radius: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vl-dropdown button:hover {
|
||||||
|
background: #090a0b;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vl-dropdown button:focus {
|
||||||
|
outline: 0;
|
||||||
|
background-color: hsl(226, 79%, 37.5%);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
227
components/vlSlider.vue
Normal file
227
components/vlSlider.vue
Normal file
@@ -0,0 +1,227 @@
|
|||||||
|
<script setup>
|
||||||
|
const props = defineProps({
|
||||||
|
min: {
|
||||||
|
type: Number,
|
||||||
|
default: 0
|
||||||
|
},
|
||||||
|
max: {
|
||||||
|
type: Number,
|
||||||
|
default: 1
|
||||||
|
},
|
||||||
|
step: {
|
||||||
|
type: Number,
|
||||||
|
default: 0.01
|
||||||
|
},
|
||||||
|
value: {
|
||||||
|
type: Number,
|
||||||
|
default: 0
|
||||||
|
},
|
||||||
|
id: {
|
||||||
|
type: String,
|
||||||
|
},
|
||||||
|
label: {
|
||||||
|
type: String,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const emit = defineEmits(['update:modelValue'])
|
||||||
|
const modelValue = ref(props.value);
|
||||||
|
|
||||||
|
watch(modelValue, (newValue) => {
|
||||||
|
emit('update:modelValue', newValue);
|
||||||
|
});
|
||||||
|
emit('update:modelValue', props.value);
|
||||||
|
|
||||||
|
function roundValueToStep(number) {
|
||||||
|
const clampedNumber = Math.min(Math.max(number, props.min), props.max);
|
||||||
|
const quotient = Math.floor((clampedNumber - props.min) / props.step);
|
||||||
|
const lowerValue = props.min + quotient * props.step;
|
||||||
|
const upperValue = lowerValue + props.step;
|
||||||
|
|
||||||
|
if (clampedNumber - lowerValue < upperValue - clampedNumber) {
|
||||||
|
return lowerValue; // Round down
|
||||||
|
} else {
|
||||||
|
return upperValue; // Round up
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const rangeSlider = ref(null);
|
||||||
|
const rangeThumb = ref(null);
|
||||||
|
const rangeValue = ref({
|
||||||
|
real: ref(roundValueToStep(props.value)),
|
||||||
|
stepped: ref(roundValueToStep(props.value))
|
||||||
|
});
|
||||||
|
|
||||||
|
const sliderPercentage = ref((rangeValue.value.real / props.max) * 100);
|
||||||
|
const steppedSliderPercentage = ref((rangeValue.value.stepped / props.max) * 100)
|
||||||
|
const steps = (props.max / props.step)
|
||||||
|
|
||||||
|
function snapToStep(percentage) {
|
||||||
|
const pixelsPerStep = rangeSlider.value.clientWidth / steps;
|
||||||
|
let step = roundValueToStep((((percentage / 100) * rangeSlider.value.clientWidth) / pixelsPerStep) * props.step);
|
||||||
|
|
||||||
|
const split = step.toString().split('.');
|
||||||
|
// fix float imprecisions by making sure the number only has at most 3 digits after the decimal point
|
||||||
|
if (split.length > 1) {
|
||||||
|
split[1] = split[1].slice(0,3);
|
||||||
|
step = parseFloat(split.join('.'));
|
||||||
|
}
|
||||||
|
|
||||||
|
rangeValue.value.stepped = step;
|
||||||
|
modelValue.value = step;
|
||||||
|
|
||||||
|
return (rangeValue.value.stepped / props.max) * 100;
|
||||||
|
}
|
||||||
|
|
||||||
|
function calculateSliderPosition(pointerEvent) {
|
||||||
|
if (!rangeSlider.value) return;
|
||||||
|
let pointerPosFromRangeStart = pointerEvent.clientX - rangeSlider.value.getClientRects()[0].x;
|
||||||
|
|
||||||
|
if (pointerPosFromRangeStart < 0) {
|
||||||
|
pointerPosFromRangeStart = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pointerPosFromRangeStart > rangeSlider.value.clientWidth) {
|
||||||
|
pointerPosFromRangeStart = rangeSlider.value.clientWidth;
|
||||||
|
}
|
||||||
|
|
||||||
|
const value = (pointerPosFromRangeStart / rangeSlider.value.clientWidth);
|
||||||
|
|
||||||
|
const newSliderPercentage = value * 100;
|
||||||
|
|
||||||
|
if (newSliderPercentage === sliderPercentage.value) return;
|
||||||
|
|
||||||
|
updateSliderPosition(newSliderPercentage);
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateSliderPosition(newSliderPercentage) {
|
||||||
|
sliderPercentage.value = newSliderPercentage;
|
||||||
|
|
||||||
|
steppedSliderPercentage.value = snapToStep(newSliderPercentage);
|
||||||
|
}
|
||||||
|
|
||||||
|
function pointerDown(pointerEvent) {
|
||||||
|
document.body.addEventListener('pointermove', calculateSliderPosition)
|
||||||
|
|
||||||
|
calculateSliderPosition(pointerEvent);
|
||||||
|
|
||||||
|
document.body.addEventListener('pointerup', () => {
|
||||||
|
rangeThumb.value.focus();
|
||||||
|
document.body.removeEventListener('pointermove', calculateSliderPosition)
|
||||||
|
}, { once: true })
|
||||||
|
}
|
||||||
|
|
||||||
|
function keydown(keyboardEvent) {
|
||||||
|
if (keyboardEvent.keyCode >= 37 && keyboardEvent.keyCode <= 40) {
|
||||||
|
keyboardEvent.preventDefault();
|
||||||
|
const direction = (keyboardEvent.key === "ArrowRight" || keyboardEvent.key === "ArrowUp") ? 'forward' : 'backward';
|
||||||
|
|
||||||
|
if (direction === 'forward') {
|
||||||
|
if (rangeValue.value.stepped + props.step > props.max) return;
|
||||||
|
rangeValue.value.stepped += props.step;
|
||||||
|
} else {
|
||||||
|
if (rangeValue.value.stepped - props.step < props.min) return;
|
||||||
|
rangeValue.value.stepped -= props.step;
|
||||||
|
}
|
||||||
|
|
||||||
|
return updateSliderPosition((rangeValue.value.stepped / props.max) * 100);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (keyboardEvent.keyCode === 36 || keyboardEvent.keyCode === 35) {
|
||||||
|
keyboardEvent.preventDefault();
|
||||||
|
|
||||||
|
if (keyboardEvent.key === "Home") {
|
||||||
|
rangeValue.value.stepped = props.min;
|
||||||
|
} else {
|
||||||
|
rangeValue.value.stepped = props.max;
|
||||||
|
}
|
||||||
|
|
||||||
|
return updateSliderPosition((rangeValue.value.stepped / props.max) * 100);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (keyboardEvent.keyCode === 33 || keyboardEvent.keyCode === 34) {
|
||||||
|
keyboardEvent.preventDefault();
|
||||||
|
const direction = (keyboardEvent.key === "PageUp") ? 'forward' : 'backward';
|
||||||
|
let multiplier = 2;
|
||||||
|
|
||||||
|
if (direction === 'forward') {
|
||||||
|
if (rangeValue.value.stepped + (props.step * multiplier) > props.max) multiplier = 1;
|
||||||
|
if (rangeValue.value.stepped + (props.step * multiplier) > props.max) return;
|
||||||
|
rangeValue.value.stepped += (props.step * multiplier);
|
||||||
|
} else {
|
||||||
|
if (rangeValue.value.stepped - (props.step * multiplier) > props.max) multiplier = 1;
|
||||||
|
if (rangeValue.value.stepped - (props.step * multiplier) > props.max) return;
|
||||||
|
rangeValue.value.stepped -= (props.step * multiplier);
|
||||||
|
}
|
||||||
|
|
||||||
|
return updateSliderPosition((rangeValue.value.stepped / props.max) * 100);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
if (props.id) {
|
||||||
|
document.body.querySelector(`[for="${props.id}"]`).addEventListener("click", () => {
|
||||||
|
rangeThumb.value.focus();
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
onUnmounted(() => {
|
||||||
|
if (props.id) {
|
||||||
|
document.body.querySelector(`[for="${props.id}"]`).removeEventListener("click", () => {
|
||||||
|
rangeThumb.value.focus();
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="vl-range-slider" ref="rangeSlider" @pointerdown="pointerDown($event)">
|
||||||
|
<span class="vl-range-track">
|
||||||
|
<span class="vl-range-track-filled" :style="'width: ' + steppedSliderPercentage + '%;'">
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
<span class="vl-range-thumb" :aria-label="props.label" :aria-labelledby="props.id" role="slider" ref="rangeThumb"
|
||||||
|
:aria-valuemin="props.min" :aria-valuemax="props.max" aria-orientation="horizontal" :aria-valuenow="rangeValue.stepped" @keydown="keydown($event)"
|
||||||
|
tabindex="0" :style="'left: ' + steppedSliderPercentage + '%;'"></span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.vl-range-slider {
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
width: 200px;
|
||||||
|
height: 20px;
|
||||||
|
margin: 4px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vl-range-track,
|
||||||
|
.vl-range-track-filled {
|
||||||
|
display: block;
|
||||||
|
height: 4px;
|
||||||
|
width: 100%;
|
||||||
|
background: #313234;
|
||||||
|
border-radius: 99999px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vl-range-track-filled {
|
||||||
|
background: hsl(226, 79%, 47.5%);
|
||||||
|
width: 0%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vl-range-thumb {
|
||||||
|
position: absolute;
|
||||||
|
display: block;
|
||||||
|
height: 20px;
|
||||||
|
width: 20px;
|
||||||
|
background: #f7f7f7;
|
||||||
|
border-radius: 99999px;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.vl-range-thumb:focus {
|
||||||
|
outline: 4px solid #b7b7b74a;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
19
components/vlToggle.vue
Normal file
19
components/vlToggle.vue
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
<script setup>
|
||||||
|
const emit = defineEmits(['update:modelValue'])
|
||||||
|
const checked = ref(false);
|
||||||
|
|
||||||
|
watch(checked, (newValue) => {
|
||||||
|
emit('update:modelValue', newValue);
|
||||||
|
});
|
||||||
|
emit('update:modelValue', checked.value);
|
||||||
|
|
||||||
|
function toggleChecked() {
|
||||||
|
checked.value = !checked.value;
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<button class="vl-toggle-button" @click="toggleChecked()" :aria-pressed="checked" :data-state="(checked) ? 'on' : 'off'">
|
||||||
|
<slot />
|
||||||
|
</button>
|
||||||
|
</template>
|
||||||
95
components/vlToggleGroup/index.vue
Normal file
95
components/vlToggleGroup/index.vue
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
<script setup>
|
||||||
|
const props = defineProps({
|
||||||
|
type: {
|
||||||
|
type: String,
|
||||||
|
default: "single"
|
||||||
|
},
|
||||||
|
defaultValue: {
|
||||||
|
type: String || Array,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const emit = defineEmits(['update:modelValue'])
|
||||||
|
|
||||||
|
const toggleGroup = ref(null);
|
||||||
|
const selected = ref((props.type === "single") ? props.defaultValue : [].concat(props.defaultValue));
|
||||||
|
|
||||||
|
watch(selected, (newValue) => {
|
||||||
|
emit('update:modelValue', newValue);
|
||||||
|
})
|
||||||
|
emit('update:modelValue', selected.value);
|
||||||
|
|
||||||
|
const items = ref([]);
|
||||||
|
|
||||||
|
function registerToggleItem(value) {
|
||||||
|
const item = { value, tabIndex: (props.defaultValue.includes(value)) ? ref('0') : ref('-1') }
|
||||||
|
const itemIndex = (items.value.push(item)) - 1
|
||||||
|
return { item, index: itemIndex };
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleItem(index) {
|
||||||
|
if (props.type === "single") {
|
||||||
|
selected.value = items.value[index].value;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (selected.value.indexOf(items.value[index].value) > -1) {
|
||||||
|
selected.value = selected.value.filter((e) => e !== items.value[index].value)
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
selected.value.push(items.value[index].value);
|
||||||
|
}
|
||||||
|
|
||||||
|
function keydown(event) {
|
||||||
|
const elements = Array.from(toggleGroup.value.children).filter(
|
||||||
|
(el) => el.nodeName.toLowerCase() === 'button'
|
||||||
|
);
|
||||||
|
|
||||||
|
if (event.key === "Home") {
|
||||||
|
event.preventDefault();
|
||||||
|
items.value.forEach((el, i) => {
|
||||||
|
if (i === 0) return items.value[i].tabIndex = '0';
|
||||||
|
return items.value[i].tabIndex = '-1';
|
||||||
|
});
|
||||||
|
elements[0].focus();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event.key === "End") {
|
||||||
|
event.preventDefault();
|
||||||
|
items.value.forEach((el, i) => {
|
||||||
|
if (i === items.value.length - 1) return items.value[i].tabIndex = '0';
|
||||||
|
return items.value[i].tabIndex = '-1';
|
||||||
|
});
|
||||||
|
elements[items.value.length-1].focus();
|
||||||
|
}
|
||||||
|
|
||||||
|
const focusedElement = document.activeElement;
|
||||||
|
const currentIndex = elements.indexOf(focusedElement);
|
||||||
|
if (event.key === "ArrowRight" || event.key === "ArrowDown") {
|
||||||
|
event.preventDefault();
|
||||||
|
const nextIndex = currentIndex < elements.length - 1 ? currentIndex + 1 : 0;
|
||||||
|
items.value.forEach((el, i) => {
|
||||||
|
if (i === nextIndex) return items.value[i].tabIndex = '0';
|
||||||
|
return items.value[i].tabIndex = '-1';
|
||||||
|
});
|
||||||
|
elements[nextIndex].focus();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event.key === "ArrowLeft" || event.key === "ArrowUp") {
|
||||||
|
event.preventDefault();
|
||||||
|
const nextIndex = currentIndex > 0 ? currentIndex - 1 : elements.length - 1;
|
||||||
|
items.value.forEach((el, i) => {
|
||||||
|
if (i === nextIndex) return items.value[i].tabIndex = '0';
|
||||||
|
return items.value[i].tabIndex = '-1';
|
||||||
|
});
|
||||||
|
elements[nextIndex].focus();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
provide('toggleGroup', { registerToggleItem, toggleItem, selected })
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div role="group" ref="toggleGroup" class="vl-toggle-group" @keydown="keydown($event)">
|
||||||
|
<slot />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
17
components/vlToggleGroup/item.vue
Normal file
17
components/vlToggleGroup/item.vue
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
<script setup>
|
||||||
|
const props = defineProps({
|
||||||
|
value: {
|
||||||
|
type: String,
|
||||||
|
default: ""
|
||||||
|
},
|
||||||
|
})
|
||||||
|
const toggleGroup = inject('toggleGroup');
|
||||||
|
|
||||||
|
const item = toggleGroup.registerToggleItem(props.value);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<button role="radio" type="button" @click="toggleGroup.toggleItem(item.index)" :aria-checked="toggleGroup.selected.value === props.value || toggleGroup.selected.value.indexOf(props.value) > -1" :tabindex="item.item.tabIndex.value" :data-state="(toggleGroup.selected.value === props.value || toggleGroup.selected.value.indexOf(props.value) > -1) ? 'checked' : 'unchecked'" class="vl-toggle-button">
|
||||||
|
<slot />
|
||||||
|
</button>
|
||||||
|
</template>
|
||||||
87
components/vlToggleSwitch.vue
Normal file
87
components/vlToggleSwitch.vue
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
<script setup>
|
||||||
|
const props = defineProps({
|
||||||
|
checked: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
id: {
|
||||||
|
type: String
|
||||||
|
},
|
||||||
|
label: {
|
||||||
|
type: String
|
||||||
|
},
|
||||||
|
disabled: {
|
||||||
|
type: Boolean
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const emit = defineEmits(['update:modelValue'])
|
||||||
|
|
||||||
|
const active = ref(props.checked);
|
||||||
|
|
||||||
|
watch(() => props.checked, (newValue) => {
|
||||||
|
active.value = newValue;
|
||||||
|
})
|
||||||
|
|
||||||
|
watch(active, (newValue) => {
|
||||||
|
emit('update:modelValue', newValue);
|
||||||
|
});
|
||||||
|
emit('update:modelValue', props.checked)
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
document.querySelector(`[for='${props.id}']`).addEventListener('click', () => {
|
||||||
|
if (props.disabled) return;
|
||||||
|
active.value = !active.value
|
||||||
|
});
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<button role="switch" class="vl-toggle-switch" :aria-disabled="(props.disabled === true) ? 'true' : 'false'"
|
||||||
|
:aria-label="label" :aria-labelledby="id" :tabindex="(disabled) ? '-1' : '0'"
|
||||||
|
@click="(!disabled) ? active = !active : ''" :aria-checked="active" :data-state="(active) ? 'checked' : 'unchecked'">
|
||||||
|
<div></div>
|
||||||
|
</button>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped> .vl-toggle-switch {
|
||||||
|
font-size: inherit;
|
||||||
|
border: 0;
|
||||||
|
cursor: pointer;
|
||||||
|
width: 2.5em;
|
||||||
|
height: 1.4em;
|
||||||
|
background: #1a1b1c;
|
||||||
|
border-radius: 100px;
|
||||||
|
padding: 0.125rem 0.25rem;
|
||||||
|
position: relative;
|
||||||
|
transition: background-color 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vl-toggle-switch[aria-disabled="true"] div {
|
||||||
|
background: #919191;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vl-toggle-switch div {
|
||||||
|
position: relative;
|
||||||
|
left: 0;
|
||||||
|
width: 1em;
|
||||||
|
height: 1em;
|
||||||
|
background: #f7f7f7;
|
||||||
|
border-radius: 90px;
|
||||||
|
pointer-events: none;
|
||||||
|
transition: all 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vl-toggle-switch[data-state="checked"] {
|
||||||
|
background: hsl(226, 79%, 47.5%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.vl-toggle-switch[data-state="checked"] div {
|
||||||
|
left: 100%;
|
||||||
|
transform: translateX(-100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.vl-toggle-switch:active div {
|
||||||
|
width: 1.3em;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
6
nuxt.config.ts
Normal file
6
nuxt.config.ts
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
// https://nuxt.com/docs/api/configuration/nuxt-config
|
||||||
|
export default defineNuxtConfig({
|
||||||
|
css: [
|
||||||
|
'~/assets/styles.css',
|
||||||
|
]
|
||||||
|
})
|
||||||
13517
package-lock.json
generated
Normal file
13517
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
15
package.json
Normal file
15
package.json
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
"name": "nuxt-app",
|
||||||
|
"private": true,
|
||||||
|
"scripts": {
|
||||||
|
"build": "nuxt build",
|
||||||
|
"dev": "nuxt dev",
|
||||||
|
"generate": "nuxt generate",
|
||||||
|
"preview": "nuxt preview",
|
||||||
|
"postinstall": "nuxt prepare"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/node": "^18",
|
||||||
|
"nuxt": "^3.5.1"
|
||||||
|
}
|
||||||
|
}
|
||||||
203
pages/index.vue
Normal file
203
pages/index.vue
Normal file
@@ -0,0 +1,203 @@
|
|||||||
|
<script setup>
|
||||||
|
const volume = ref(25);
|
||||||
|
const airplaneMode = ref(false);
|
||||||
|
const toggle = ref(false);
|
||||||
|
const group = ref('center');
|
||||||
|
const burgerIngredients = ref(['tomato', 'cheese']);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="presentation">
|
||||||
|
<div class="container">
|
||||||
|
<button class="vl-button">
|
||||||
|
Button
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="container">
|
||||||
|
<Transition>
|
||||||
|
<VlDropdown>
|
||||||
|
<button>
|
||||||
|
Button
|
||||||
|
</button>
|
||||||
|
<button>
|
||||||
|
Button
|
||||||
|
</button>
|
||||||
|
<button>
|
||||||
|
Button
|
||||||
|
</button>
|
||||||
|
</VlDropdown>
|
||||||
|
</Transition>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="container">
|
||||||
|
<div>
|
||||||
|
{{ group }}
|
||||||
|
<VlToggleGroup v-model="group" type="single" :defaultValue="group" aria-label="Text alignment">
|
||||||
|
<VlToggleGroupItem aria-label="Left aligned" value="left">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24">
|
||||||
|
<path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||||
|
d="M4 6h16M4 12h10M4 18h14" />
|
||||||
|
</svg>
|
||||||
|
</VlToggleGroupItem>
|
||||||
|
|
||||||
|
<VlToggleGroupItem aria-label="Center aligned" value="center">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24">
|
||||||
|
<path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||||
|
d="M4 6h16M8 12h8M6 18h12" />
|
||||||
|
</svg>
|
||||||
|
</VlToggleGroupItem>
|
||||||
|
|
||||||
|
<VlToggleGroupItem aria-label="Right aligned" value="right">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24">
|
||||||
|
<path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||||
|
d="M4 6h16m-10 6h10M6 18h14" />
|
||||||
|
</svg>
|
||||||
|
</VlToggleGroupItem>
|
||||||
|
</VlToggleGroup>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
{{ burgerIngredients }}
|
||||||
|
<VlToggleGroup v-model="burgerIngredients" type="multiple" :defaultValue="burgerIngredients" aria-label="Burger ingredients">
|
||||||
|
<VlToggleGroupItem aria-label="Cheese" value="cheese">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24">
|
||||||
|
<g fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2">
|
||||||
|
<path d="M4.519 20.008L21 20v-3.5a2 2 0 1 1 0-4V9H4.278" />
|
||||||
|
<path
|
||||||
|
d="m21 9l-9.385-4.992c-2.512.12-4.758 1.42-6.327 3.425C3.865 9.253 3 11.654 3 14.287c0 2.117.56 4.085 1.519 5.721M15 13v.01M8 13v.01M11 16v.01" />
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
</VlToggleGroupItem>
|
||||||
|
|
||||||
|
<VlToggleGroupItem aria-label="Tomato" value="tomato">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 48 48">
|
||||||
|
<g fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="4">
|
||||||
|
<path
|
||||||
|
d="M24 44c11.046 0 20-7.387 20-16.5c0-6.442-4.475-11.799-11-14.516L29.5 14.5L30 20l-6.5-2l-6.5 2v-5.5l-3-1.516C8.022 15.837 4 21.393 4 27.5C4 36.613 12.954 44 24 44Z" />
|
||||||
|
<path
|
||||||
|
d="m23.5 4l3.809 5.117L36 9.91l-6.337 4.573L31.5 21l-8-3l-8 3l1.837-6.517L11 9.91l8.691-.793L23.5 4Z" />
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
</VlToggleGroupItem>
|
||||||
|
|
||||||
|
<VlToggleGroupItem aria-label="Onions" value="onion">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 32 32">
|
||||||
|
<path fill="currentColor"
|
||||||
|
d="M12.74 2.458c0-1.333 1.658-1.981 2.553-.959l1.182 1.354L17.657 1.5c.895-1.023 2.553-.375 2.553.958v1.83c0 .738.44 1.405 1.136 1.712c4.464 1.892 7.604 6.325 7.604 11.488c0 6.076-4.349 11.146-10.11 12.25v.01c0 .69-.56 1.25-1.25 1.25h-2.16c-.69 0-1.25-.56-1.25-1.25v-.005C8.458 28.632 4 23.438 4 17.478c0-5.166 3.143-9.589 7.608-11.48a1.86 1.86 0 0 0 1.132-1.7v-1.84Zm2 1.449v.39a3.86 3.86 0 0 1-2.346 3.54l-.004.002c-.184.077-.364.16-.542.248c-1.96 2.085-3.118 5.341-3.118 8.84c0 5.31 2.645 9.753 6.153 10.805c-1.346-2.014-2.063-6.514-2.063-10.814c0-3.76.5-7.21 1.37-9.44l.75.29c-.82 2.11-1.31 5.53-1.31 9.15c0 3.03.33 5.88.94 8.02c.552 1.949 1.2 2.82 1.69 3.022c.147.003.295.003.441 0c.489-.203 1.137-1.074 1.689-3.022c.6-2.14.94-4.98.94-8.02c0-3.61-.49-7.03-1.31-9.15l.75-.29c.87 2.24 1.37 5.68 1.37 9.44c-.007 4.3-.72 8.801-2.064 10.814c3.508-1.05 6.154-5.494 6.154-10.804c0-3.495-1.155-6.746-3.109-8.832a10.358 10.358 0 0 0-.56-.257l-.012-.005a3.874 3.874 0 0 1-2.339-3.546v-.381l-.635.726a1.456 1.456 0 0 1-2.186.016l-.006-.007l-.643-.735ZM9.67 9.52A10.437 10.437 0 0 0 6 17.478c0 3.797 2.18 7.24 5.335 9.08c-.365-.372-.71-.786-1.035-1.24c-1.61-2.25-2.49-5.23-2.49-8.39c0-2.728.67-5.326 1.86-7.407Zm11.817 17.167a10.474 10.474 0 0 0 5.463-9.2c0-3.176-1.416-6.025-3.649-7.947c1.184 2.078 1.849 4.668 1.849 7.387c0 3.16-.88 6.14-2.49 8.39a10.42 10.42 0 0 1-1.173 1.37Z" />
|
||||||
|
</svg>
|
||||||
|
</VlToggleGroupItem>
|
||||||
|
</VlToggleGroup>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="container">
|
||||||
|
<div style="display: flex; gap: 6px 0; flex-direction: column;">
|
||||||
|
<div style="display: flex;">
|
||||||
|
<label for="airplane-mode" style=" margin-right: 8px;">Airplane mode</label>
|
||||||
|
<VlToggleSwitch :checked="airplaneMode" v-model="airplaneMode" id="airplane-mode" />
|
||||||
|
</div>
|
||||||
|
<div style="display: flex;">
|
||||||
|
<label for="wifi" style=" margin-right: 8px;">Wifi</label>
|
||||||
|
<VlToggleSwitch :checked="!airplaneMode" :disabled="(airplaneMode) ? true : false" id="wifi" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="container">
|
||||||
|
<div>
|
||||||
|
<label for="Volume">Volume: {{ volume }}</label>
|
||||||
|
<VlSlider :value="volume" v-model="volume" id="Volume" :step="1" :max="100" />
|
||||||
|
</div>
|
||||||
|
<VlSlider label="Volume" :step="1" :min="0" :max="10" />
|
||||||
|
<VlSlider label="Volume" :value="1" :min="0" :max="9" :step="3" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="container">
|
||||||
|
<VlAccordion type="single" defaultValue="item-1">
|
||||||
|
<VlAccordionItem value="item-1">
|
||||||
|
<VlAccordionTrigger>
|
||||||
|
Section 1
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24">
|
||||||
|
<path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||||
|
d="m6 9l6 6l6-6" />
|
||||||
|
</svg>
|
||||||
|
</VlAccordionTrigger>
|
||||||
|
<VlAccordionContent>
|
||||||
|
Content for section 1
|
||||||
|
</VlAccordionContent>
|
||||||
|
</VlAccordionItem>
|
||||||
|
<VlAccordionItem value="item-2">
|
||||||
|
<VlAccordionTrigger>
|
||||||
|
Section 2
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24">
|
||||||
|
<path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||||
|
d="m6 9l6 6l6-6" />
|
||||||
|
</svg>
|
||||||
|
</VlAccordionTrigger>
|
||||||
|
<VlAccordionContent>
|
||||||
|
Content for section 2
|
||||||
|
</VlAccordionContent>
|
||||||
|
</VlAccordionItem>
|
||||||
|
<VlAccordionItem value="item-3">
|
||||||
|
<VlAccordionTrigger>
|
||||||
|
Section 3
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24">
|
||||||
|
<path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||||
|
d="m6 9l6 6l6-6" />
|
||||||
|
</svg>
|
||||||
|
</VlAccordionTrigger>
|
||||||
|
<VlAccordionContent>
|
||||||
|
Content for section 3
|
||||||
|
</VlAccordionContent>
|
||||||
|
</VlAccordionItem>
|
||||||
|
<VlAccordionItem value="item-4">
|
||||||
|
<VlAccordionTrigger>
|
||||||
|
Section 4
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24">
|
||||||
|
<path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||||
|
d="m6 9l6 6l6-6" />
|
||||||
|
</svg>
|
||||||
|
</VlAccordionTrigger>
|
||||||
|
<VlAccordionContent>
|
||||||
|
Content for section 4
|
||||||
|
</VlAccordionContent>
|
||||||
|
</VlAccordionItem>
|
||||||
|
</VlAccordion>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="container">
|
||||||
|
<div>
|
||||||
|
{{ toggle }}
|
||||||
|
<VlToggle v-model="toggle" aria-label="Toggle italic">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24">
|
||||||
|
<path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||||
|
d="M11 5h6M7 19h6m1-14l-4 14" />
|
||||||
|
</svg>
|
||||||
|
</VlToggle>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.presentation {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
|
min-height: calc(100vh - 24px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.container {
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
display: grid;
|
||||||
|
place-items: center;
|
||||||
|
width: 420px;
|
||||||
|
height: 275px;
|
||||||
|
background: #121314;
|
||||||
|
border-radius: 12px;
|
||||||
|
border: 1px solid #232425;
|
||||||
|
box-shadow: 0px 0px 12px #00000012;
|
||||||
|
}</style>
|
||||||
BIN
public/favicon.ico
Normal file
BIN
public/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 4.2 KiB |
3
server/tsconfig.json
Normal file
3
server/tsconfig.json
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"extends": "../.nuxt/tsconfig.server.json"
|
||||||
|
}
|
||||||
26
tsconfig.json
Normal file
26
tsconfig.json
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "ES2020",
|
||||||
|
"lib": [
|
||||||
|
"ESNext",
|
||||||
|
"ESNext.AsyncIterable",
|
||||||
|
"DOM"
|
||||||
|
],
|
||||||
|
"strict": true,
|
||||||
|
"sourceMap": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"forceConsistentCasingInFileNames": true,
|
||||||
|
"noEmit": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"module": "ESNext",
|
||||||
|
"moduleResolution": "Node",
|
||||||
|
"resolveJsonModule": true,
|
||||||
|
"isolatedModules": true,
|
||||||
|
"incremental": true,
|
||||||
|
"jsx": "preserve",
|
||||||
|
"noUncheckedIndexedAccess": true,
|
||||||
|
"noImplicitAny": true,
|
||||||
|
"allowJs": true
|
||||||
|
},
|
||||||
|
"extends": "./.nuxt/tsconfig.json"
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user