Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
85 changes: 85 additions & 0 deletions packages/core/src/Combobox/story/ComboboxShadowRoot.story.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
<script setup lang="ts">
import type { Component } from 'vue'
import { createApp, useTemplateRef, watch } from 'vue'

import _Dialog from './_Dialog.vue'

const options = [
{ name: 'Fruit', children: [
{ name: 'Apple' },
{ name: 'Banana' },
{ name: 'Orange' },
{ name: 'Honeydew' },
{ name: 'Grapes' },
{ name: 'Watermelon' },
{ name: 'Cantaloupe' },
{ name: 'Pear' },
] },
{ name: 'Vegetable', children: [
{ name: 'Cabbage' },
{ name: 'Broccoli' },
{ name: 'Carrots' },
{ name: 'Lettuce' },
{ name: 'Spinach' },
{ name: 'Bok Choy' },
{ name: 'Cauliflower' },
{ name: 'Potatoes' },
] },
]

function mountShadowRoot(container: HTMLDivElement, component: Component) {
const elementWithShadow = container as Element & { shadowRoot: ShadowRoot | null }
const shadowRoot
= elementWithShadow.shadowRoot || elementWithShadow.attachShadow({ mode: 'open' })

document.querySelectorAll('style, link[rel="stylesheet"]').forEach((node) => {
shadowRoot.appendChild(node.cloneNode(true))
})

const shadowMountPoint = document.createElement('div')
shadowRoot.appendChild(shadowMountPoint)

const shadowPortalTarget = document.createElement('div')
shadowPortalTarget.id = `portal-shadow-root`
shadowRoot.appendChild(shadowPortalTarget)

createApp(component, {
dialogPortalTarget: shadowPortalTarget,
}).mount(shadowMountPoint)
}

function resetShadowRoot(container: HTMLDivElement) {
const elementWithShadow = container as Element & { shadowRoot: ShadowRoot | null }
if (elementWithShadow.shadowRoot) {
elementWithShadow.shadowRoot.innerHTML = ''
}
}

const container = useTemplateRef('container')

watch(
container,
(newVal) => {
if (newVal) {
resetShadowRoot(newVal)
mountShadowRoot(newVal, _Dialog)
}
},
{ immediate: true },
)
</script>

<template>
<Story
title="Combobox/ShadowRoot"
:layout="{ type: 'single', iframe: false }"
>
<Variant title="default">
<div
id="shadow-root-container"
ref="container"
class="h-96"
/>
</Variant>
</Story>
</template>
152 changes: 152 additions & 0 deletions packages/core/src/Combobox/story/_Dialog.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
<script setup lang="ts">
import { Icon } from '@iconify/vue'
import { ref } from 'vue'
import { ComboboxAnchor, ComboboxContent, ComboboxGroup, ComboboxInput, ComboboxItem, ComboboxItemIndicator, ComboboxLabel, ComboboxRoot, ComboboxSeparator, ComboboxTrigger, ComboboxViewport } from '..'
import {
DialogClose,
DialogContent,
DialogOverlay,
DialogPortal,
DialogRoot,
DialogTitle,
DialogTrigger,
} from '../../Dialog'

import ComboboxManualFilter from './_ComboboxManualFilter.vue'

defineProps<{ dialogPortalTarget: HTMLDivElement }>()
const v = ref('Apple')
const searchTerm = ref('')
const options = ['Apple', 'Banana', 'Blueberry', 'Grapes', 'Pineapple']
const vegetables = ['Aubergine', 'Broccoli', 'Carrot', 'Courgette', 'Leek']

const dialogOpen = ref(false)
</script>

<template>
<DialogRoot v-model:open="dialogOpen">
<DialogTrigger
class="text-violet11 shadow-blackA7 hover:bg-mauve3 inline-flex h-[35px] items-center justify-center rounded-[4px] bg-white px-[15px] font-medium leading-none shadow-[0_2px_10px] focus:shadow-[0_0_0_2px] focus:shadow-black focus:outline-none"
>
Edit profile
</DialogTrigger>
<DialogPortal :to="dialogPortalTarget">
<Transition name="fade">
<DialogOverlay
class="bg-blackA9 fixed inset-0"
/>
</Transition>
<Transition name="fade">
<DialogContent
:is-escape-key-down-default="true"
class="fixed top-[50%] left-[50%] max-h-[85vh] w-[90vw] max-w-[450px] translate-x-[-50%] translate-y-[-50%] rounded-[6px] bg-white p-[25px] shadow-[hsl(206_22%_7%_/_35%)_0px_10px_38px_-10px,_hsl(206_22%_7%_/_20%)_0px_10px_20px_-15px] focus:outline-none"
@pointer-down-outside.prevent
>
<DialogTitle class="text-mauve12 m-0 text-[17px] font-medium">
Edit profile
</DialogTitle>
<ComboboxManualFilter />
<div class="flex flex-col">
<button @click="v = 'Banana'">
Set search term {{ searchTerm }} to "Banana"
</button>
<ComboboxRoot
v-model="v"
ignore-filter
>
<ComboboxAnchor class="w-full min-w-[160px] inline-flex items-center justify-between rounded px-[15px] text-[13px] leading-none h-[35px] gap-[5px] bg-white text-grass11 shadow-[0_2px_10px] shadow-black/10 hover:bg-mauve3 focus:shadow-[0_0_0_2px] focus:shadow-black data-[placeholder]:text-grass9 outline-none">
<ComboboxInput
v-model="searchTerm"
class=" bg-transparent w-full outline-none text-grass11 placeholder-gray-400"
placeholder="Test"
/>
<ComboboxTrigger>
<Icon
icon="radix-icons:chevron-down"
class="h-4 w-4 text-grass11"
/>
</ComboboxTrigger>
</ComboboxAnchor>
<ComboboxContent class="mt-2 min-w-[160px] bg-white overflow-hidden rounded shadow-[0px_10px_38px_-10px_rgba(22,_23,_24,_0.35),_0px_10px_20px_-15px_rgba(22,_23,_24,_0.2)] will-change-[opacity,transform] data-[side=top]:animate-slideDownAndFade data-[side=right]:animate-slideLeftAndFade data-[side=bottom]:animate-slideUpAndFade data-[side=left]:animate-slideRightAndFade">
<ComboboxViewport class="p-[5px]">
<ComboboxGroup>
<ComboboxLabel class="px-[25px] text-xs leading-[25px] text-mauve11">
Fruits
</ComboboxLabel>

<ComboboxItem
v-for="(option, index) in options"
:key="index"
class="text-[13px] leading-none text-grass11 rounded-[3px] flex items-center h-[25px] pr-[35px] pl-[25px] relative select-none data-[disabled]:text-mauve8 data-[disabled]:pointer-events-none data-[highlighted]:outline-none data-[highlighted]:bg-grass9 data-[highlighted]:text-grass1"
:value="option"
>
<ComboboxItemIndicator
class="absolute left-0 w-[25px] inline-flex items-center justify-center"
>
<Icon icon="radix-icons:check" />
</ComboboxItemIndicator>
<span>
{{ option }}
</span>
</ComboboxItem>
<ComboboxSeparator class="h-[1px] bg-grass6 m-[5px]" />
</ComboboxGroup>

<ComboboxGroup>
<ComboboxLabel
class="px-[25px] text-xs leading-[25px] text-mauve11"
>
Vegetables
</ComboboxLabel>
<ComboboxItem
v-for="(option, index) in vegetables"
:key="index"
class="text-[13px] leading-none text-grass11 rounded-[3px] flex items-center h-[25px] pr-[35px] pl-[25px] relative select-none data-[disabled]:text-mauve8 data-[disabled]:pointer-events-none data-[highlighted]:outline-none data-[highlighted]:bg-grass9 data-[highlighted]:text-grass1"
:value="option"
>
<ComboboxItemIndicator
class="absolute left-0 w-[25px] inline-flex items-center justify-center"
>
<Icon icon="radix-icons:check" />
</ComboboxItemIndicator>
<span>
{{ option }}
</span>
</ComboboxItem>
</ComboboxGroup>
</ComboboxViewport>
</ComboboxContent>
</ComboboxRoot>
</div>
<div class="mt-[25px] flex justify-end">
<DialogClose as-child>
<button
class="bg-green4 text-green11 hover:bg-green5 focus:shadow-green7 inline-flex h-[35px] items-center justify-center rounded-[4px] px-[15px] font-medium leading-none focus:shadow-[0_0_0_2px] focus:outline-none"
>
Save changes
</button>
</DialogClose>
</div>
<DialogClose
class="text-violet11 hover:bg-violet4 focus:shadow-violet7 absolute top-[10px] right-[10px] inline-flex h-[25px] w-[25px] appearance-none items-center justify-center rounded-full focus:shadow-[0_0_0_2px] focus:outline-none"
aria-label="Close"
>
<Icon icon="lucide:x" />
</DialogClose>
</DialogContent>
</Transition>
</DialogPortal>
</DialogRoot>
</template>

<style>
.fade-enter-active,
.fade-leave-active {
transition: opacity 0.3s ease;
}

.fade-enter-from,
.fade-leave-to {
opacity: 0;
}
</style>
61 changes: 61 additions & 0 deletions packages/core/src/Dialog/story/DialogShadowroot.story.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
<script setup lang="ts">
import type { Component } from 'vue'
import { createApp, useTemplateRef, watch } from 'vue'
import DialogShadowRoot from './shadowRoot/DialogShadowRoot.vue'

function mountShadowRoot(container: HTMLDivElement, component: Component) {
const elementWithShadow = container as Element & { shadowRoot: ShadowRoot | null }
const shadowRoot
= elementWithShadow.shadowRoot || elementWithShadow.attachShadow({ mode: 'open' })

document.querySelectorAll('style, link[rel="stylesheet"]').forEach((node) => {
shadowRoot.appendChild(node.cloneNode(true))
})

const shadowMountPoint = document.createElement('div')
shadowRoot.appendChild(shadowMountPoint)

const shadowPortalTarget = document.createElement('div')
shadowPortalTarget.id = `portal-shadow-root`
shadowRoot.appendChild(shadowPortalTarget)

createApp(component, {
portalTarget: shadowPortalTarget,
}).mount(shadowMountPoint)
}

function resetShadowRoot(container: HTMLDivElement) {
const elementWithShadow = container as Element & { shadowRoot: ShadowRoot | null }
if (elementWithShadow.shadowRoot) {
elementWithShadow.shadowRoot.innerHTML = ''
}
}

const container = useTemplateRef('container')

watch(
container,
(newVal) => {
if (newVal) {
resetShadowRoot(newVal)
mountShadowRoot(newVal, DialogShadowRoot)
}
},
{ immediate: true },
)
</script>

<template>
<Story
title="Dialog/Shadowroot"
:layout="{ type: 'single', iframe: false }"
>
<Variant title="default">
<div
id="shadow-root-container"
ref="container"
class="h-96"
/>
</Variant>
</Story>
</template>
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
<script setup lang="ts">
import { Icon } from '@iconify/vue'
import { computed, ref } from 'vue'
import { ComboboxAnchor, ComboboxContent, ComboboxGroup, ComboboxInput, ComboboxItem, ComboboxItemIndicator, ComboboxLabel, ComboboxPortal, ComboboxRoot, ComboboxTrigger, ComboboxViewport } from '../../../Combobox'

defineProps<{ portalTarget: HTMLDivElement }>()

const people = [
{ id: 1, name: 'Durward Reynolds' },
{ id: 2, name: 'Kenton Towne' },
{ id: 3, name: 'Therese Wunsch' },
{ id: 4, name: 'Benedict Kessler' },
{ id: 5, name: 'Katelyn Rohan' },
]

const searchTerm = ref('')
const v = ref([people[0]])

const filteredPeople = computed(() => people.filter(i => i.name.toLowerCase().includes(searchTerm.value.toLowerCase())))

function handleUpdate(ev: any) {
// eslint-disable-next-line no-console
console.log(ev)
}
</script>

<template>
<ComboboxRoot multiple>
<ComboboxAnchor class="min-w-[160px] inline-flex items-center justify-between rounded px-[15px] text-[13px] leading-none h-[35px] gap-[5px] bg-white text-grass11 shadow-[0_2px_10px] shadow-black/10 hover:bg-mauve3 focus:shadow-[0_0_0_2px] focus:shadow-black data-[placeholder]:text-grass9 outline-none">
<ComboboxInput
class="bg-transparent outline-none text-grass11 placeholder-gray-400"
placeholder="Test"
/>
<ComboboxTrigger>
<Icon
icon="radix-icons:chevron-down"
class="h-4 w-4 text-grass11"
/>
</ComboboxTrigger>
</ComboboxAnchor>
<ComboboxPortal :to="portalTarget">
<ComboboxContent class="mt-2 min-w-[160px] bg-white overflow-hidden rounded shadow-[0px_10px_38px_-10px_rgba(22,_23,_24,_0.35),_0px_10px_20px_-15px_rgba(22,_23,_24,_0.2)] will-change-[opacity,transform] data-[side=top]:animate-slideDownAndFade data-[side=right]:animate-slideLeftAndFade data-[side=bottom]:animate-slideUpAndFade data-[side=left]:animate-slideRightAndFade">
<ComboboxViewport class="p-[5px]">
<ComboboxGroup>
<ComboboxLabel class="px-[25px] text-xs leading-[25px] text-mauve11">
People
</ComboboxLabel>

<ComboboxItem
v-for="option in filteredPeople.map(i => i.name)"
:key="option"
class="text-[13px] leading-none text-grass11 rounded-[3px] flex items-center h-[25px] pr-[35px] pl-[25px] relative select-none data-[disabled]:text-mauve8 data-[disabled]:pointer-events-none data-[highlighted]:outline-none data-[highlighted]:bg-grass9 data-[highlighted]:text-grass1"
:value="option"
>
<ComboboxItemIndicator
class="absolute left-0 w-[25px] inline-flex items-center justify-center"
>
<Icon icon="radix-icons:check" />
</ComboboxItemIndicator>
<span>
{{ option }}
</span>
</ComboboxItem>
</ComboboxGroup>
</ComboboxViewport>
</ComboboxContent>
</ComboboxPortal>
</ComboboxRoot>
</template>
Loading