-
-
Notifications
You must be signed in to change notification settings - Fork 164
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* init branch dropdownmenu * built * save * add basic item * add esc to closemodal, add focus trigger after close, add keydown arrowdown to open modal * handle keydown arrowup arrowdown when nothing selected * enable disabled api * enable checkbox click, keyboard enter and space * dropdownmenu 1/3 w/o nested
- Loading branch information
1 parent
8c7fd34
commit c7e4272
Showing
18 changed files
with
937 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
124 changes: 124 additions & 0 deletions
124
packages/playground-vue/src/components/Demo/DropdownMenuDemo.vue
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,124 @@ | ||
<script setup lang="ts"> | ||
import { | ||
DropdownMenuArrow, | ||
DropdownMenuContent, | ||
DropdownMenuItem, | ||
DropdownMenuPortal, | ||
DropdownMenuRoot, | ||
DropdownMenuSeparator, | ||
DropdownMenuTrigger, | ||
DropdownMenuCheckboxItem, | ||
DropdownMenuItemIndicator, | ||
DropdownMenuLabel, | ||
DropdownMenuRadioGroup, | ||
DropdownMenuRadioItem, | ||
} from "radix-vue"; | ||
import { Icon } from "@iconify/vue"; | ||
import { ref } from "vue"; | ||
const toggleState = ref(false); | ||
const checkboxOne = ref(false) | ||
const checkboxTwo = ref(false) | ||
const person = ref("pedro") | ||
function handleClick(){ | ||
alert('hello!') | ||
} | ||
</script> | ||
|
||
<template> | ||
<div class="absolute left-4 top-3 text-sm"> | ||
<p>Dropdown Open: {{ toggleState ? "open" : "close" }}</p> | ||
<p>Checkbox 1: {{ checkboxOne ? "checked" : "unchecked" }}</p> | ||
<p>Checkbox 2: {{ checkboxTwo ? "checked" : "unchecked" }}</p> | ||
<p>Person: {{ person }}</p> | ||
</div> | ||
<DropdownMenuRoot v-model="toggleState"> | ||
<DropdownMenuTrigger class="rounded-full w-[35px] h-[35px] inline-flex items-center justify-center text-violet11 bg-white shadow-[0_2px_10px] shadow-blackA7 outline-none hover:bg-violet3 focus:shadow-[0_0_0_2px] focus:shadow-black" | ||
aria-label="Customise options"> | ||
<Icon icon="radix-icons:hamburger-menu" /> | ||
</DropdownMenuTrigger> | ||
|
||
<DropdownMenuPortal> | ||
<DropdownMenuContent | ||
class="min-w-[220px] bg-white rounded-md p-[5px] 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" | ||
sideOffset={5}> | ||
<DropdownMenuItem value="New Tab" @click="handleClick" | ||
class="group text-[13px] leading-none text-violet11 rounded-[3px] flex items-center h-[25px] px-[5px] relative pl-[25px] select-none outline-none data-[disabled]:text-mauve8 data-[disabled]:pointer-events-none data-[highlighted]:bg-violet9 data-[highlighted]:text-violet1"> | ||
New Tab | ||
<div | ||
class="ml-auto pl-[20px] text-mauve11 group-data-[highlighted]:text-white group-data-[disabled]:text-mauve8"> | ||
⌘+T | ||
</div> | ||
</DropdownMenuItem> | ||
<DropdownMenuItem value="New Window" | ||
class="group text-[13px] leading-none text-violet11 rounded-[3px] flex items-center h-[25px] px-[5px] relative pl-[25px] select-none outline-none data-[disabled]:text-mauve8 data-[disabled]:pointer-events-none data-[highlighted]:bg-violet9 data-[highlighted]:text-violet1"> | ||
New Window | ||
<div | ||
class="ml-auto pl-[20px] text-mauve11 group-data-[highlighted]:text-white group-data-[disabled]:text-mauve8"> | ||
⌘+N | ||
</div> | ||
</DropdownMenuItem> | ||
<DropdownMenuItem value="New Private Window" | ||
class="group text-[13px] leading-none text-violet11 rounded-[3px] flex items-center h-[25px] px-[5px] relative pl-[25px] select-none outline-none data-[disabled]:text-mauve8 data-[disabled]:pointer-events-none data-[highlighted]:bg-violet9 data-[highlighted]:text-violet1" | ||
disabled> | ||
New Private Window | ||
<div | ||
class="ml-auto pl-[20px] text-mauve11 group-data-[highlighted]:text-white group-data-[disabled]:text-mauve8"> | ||
⇧+⌘+N | ||
</div> | ||
</DropdownMenuItem> | ||
<DropdownMenuSeparator class="h-[1px] bg-violet6 m-[5px]" /> | ||
<DropdownMenuCheckboxItem v-model="checkboxOne" | ||
class="group text-[13px] leading-none text-violet11 rounded-[3px] flex items-center h-[25px] px-[5px] relative pl-[25px] select-none outline-none data-[disabled]:text-mauve8 data-[disabled]:pointer-events-none data-[highlighted]:bg-violet9 data-[highlighted]:text-violet1" | ||
checked={bookmarksChecked} | ||
onCheckedChange={setBookmarksChecked} | ||
> | ||
<DropdownMenuItemIndicator class="absolute left-0 w-[25px] inline-flex items-center justify-center"> | ||
<Icon icon="radix-icons:check" /> | ||
</DropdownMenuItemIndicator> | ||
Show Bookmarks | ||
<div class="ml-auto pl-[20px] text-mauve11 group-data-[highlighted]:text-white group-data-[disabled]:text-mauve8"> | ||
⌘+B | ||
</div> | ||
</DropdownMenuCheckboxItem> | ||
<DropdownMenuCheckboxItem v-model="checkboxTwo" | ||
class="text-[13px] leading-none text-violet11 rounded-[3px] flex items-center h-[25px] px-[5px] relative pl-[25px] select-none outline-none data-[disabled]:text-mauve8 data-[disabled]:pointer-events-none data-[highlighted]:bg-violet9 data-[highlighted]:text-violet1" | ||
checked={urlsChecked} | ||
onCheckedChange={setUrlsChecked} | ||
> | ||
<DropdownMenuItemIndicator class="absolute left-0 w-[25px] inline-flex items-center justify-center"> | ||
<Icon icon="radix-icons:check" /> | ||
</DropdownMenuItemIndicator> | ||
Show Full URLs | ||
</DropdownMenuCheckboxItem> | ||
<DropdownMenuSeparator class="h-[1px] bg-violet6 m-[5px]" /> | ||
|
||
<DropdownMenuLabel class="pl-[25px] text-xs leading-[25px] text-mauve11"> | ||
People | ||
</DropdownMenuLabel> | ||
<DropdownMenuRadioGroup v-model="person"> | ||
<DropdownMenuRadioItem | ||
class="text-[13px] leading-none text-violet11 rounded-[3px] flex items-center h-[25px] px-[5px] relative pl-[25px] select-none outline-none data-[disabled]:text-mauve8 data-[disabled]:pointer-events-none data-[highlighted]:bg-violet9 data-[highlighted]:text-violet1" | ||
value="pedro" | ||
> | ||
<DropdownMenuItemIndicator class="absolute left-0 w-[25px] inline-flex items-center justify-center"> | ||
<Icon icon="radix-icons:dot-filled" /> | ||
</DropdownMenuItemIndicator> | ||
Pedro Duarte | ||
</DropdownMenuRadioItem> | ||
<DropdownMenuRadioItem | ||
class="text-[13px] leading-none text-violet11 rounded-[3px] flex items-center h-[25px] px-[5px] relative pl-[25px] select-none outline-none data-[disabled]:text-mauve8 data-[disabled]:pointer-events-none data-[highlighted]:bg-violet9 data-[highlighted]:text-violet1" | ||
value="colm" | ||
> | ||
<DropdownMenuItemIndicator class="absolute left-0 w-[25px] inline-flex items-center justify-center"> | ||
<Icon icon="radix-icons:dot-filled" /> | ||
</DropdownMenuItemIndicator> | ||
Colm Tuite | ||
</DropdownMenuRadioItem> | ||
</DropdownMenuRadioGroup> | ||
<DropdownMenuArrow class="fill-white" /> | ||
</DropdownMenuContent> | ||
</DropdownMenuPortal> | ||
</DropdownMenuRoot> | ||
</template> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
<script setup lang="ts"> | ||
import { inject, ref, onMounted } from "vue"; | ||
import { | ||
DROPDOWN_MENU_INJECTION_KEY, | ||
type DropdownMenuProvideValue, | ||
} from "./DropdownMenuRoot.vue"; | ||
const injectedValue = inject<DropdownMenuProvideValue>( | ||
DROPDOWN_MENU_INJECTION_KEY | ||
); | ||
const props = defineProps({ | ||
class: String, | ||
}); | ||
const arrowElement = ref<HTMLElement>(); | ||
onMounted(() => { | ||
injectedValue!.arrowElement.value = arrowElement.value; | ||
}); | ||
</script> | ||
|
||
<template> | ||
<span ref="arrowElement" style="position: absolute"> | ||
<svg | ||
:class="props.class" | ||
width="10" | ||
height="5" | ||
viewBox="0 0 30 10" | ||
preserveAspectRatio="none" | ||
style="display: block" | ||
> | ||
<polygon points="0,0 30,0 15,10"></polygon> | ||
</svg> | ||
</span> | ||
</template> |
130 changes: 130 additions & 0 deletions
130
packages/radix-vue/src/DropdownMenu/DropdownMenuCheckboxItem.vue
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,130 @@ | ||
<script setup lang="ts"> | ||
import type { Ref } from "vue"; | ||
import { ref, inject, watchEffect, provide, toRef } from "vue"; | ||
import { | ||
DROPDOWN_MENU_INJECTION_KEY, | ||
type DropdownMenuProvideValue, | ||
} from "./DropdownMenuRoot.vue"; | ||
interface DropdownMenuCheckboxItemProps { | ||
modelValue?: boolean; | ||
id?: string; | ||
name?: string; | ||
value?: string; | ||
disabled?: boolean; | ||
} | ||
export type DropdownMenuCheckboxProvideValue = Readonly<Ref<boolean>>; | ||
provide<DropdownMenuCheckboxProvideValue>( | ||
"modelValue", | ||
toRef(() => props.modelValue) | ||
); | ||
const injectedValue = inject<DropdownMenuProvideValue>( | ||
DROPDOWN_MENU_INJECTION_KEY | ||
); | ||
const props = defineProps<DropdownMenuCheckboxItemProps>(); | ||
const emit = defineEmits<{ | ||
(e: "update:modelValue", value: boolean): void; | ||
}>(); | ||
const currentElement = ref<HTMLElement | undefined>(); | ||
function handleKeydown(e: KeyboardEvent) { | ||
if (e.key === "Escape") { | ||
handleCloseMenu(); | ||
} | ||
const allToggleItem = injectedValue!.itemsArray; | ||
if (allToggleItem.length) { | ||
const currentTabIndex = allToggleItem.indexOf(currentElement.value!); | ||
if (e.key === "ArrowDown") { | ||
e.preventDefault(); | ||
if (!injectedValue?.selectedElement.value) { | ||
injectedValue?.changeSelected(allToggleItem[0]); | ||
} else if (allToggleItem[currentTabIndex + 1]) { | ||
injectedValue?.changeSelected(allToggleItem[currentTabIndex + 1]); | ||
} else { | ||
injectedValue?.changeSelected(allToggleItem[0]); | ||
} | ||
} | ||
if (e.key === "ArrowUp") { | ||
e.preventDefault(); | ||
if (!injectedValue?.selectedElement.value) { | ||
injectedValue?.changeSelected(allToggleItem[allToggleItem.length - 1]); | ||
} else if (allToggleItem[currentTabIndex - 1]) { | ||
injectedValue?.changeSelected(allToggleItem[currentTabIndex - 1]); | ||
} else { | ||
injectedValue?.changeSelected(allToggleItem[allToggleItem.length - 1]); | ||
} | ||
} | ||
if (e.keyCode === 32 || e.key === "Enter") { | ||
if (injectedValue?.selectedElement.value) { | ||
updateModelValue(); | ||
} | ||
} | ||
} | ||
} | ||
watchEffect(() => { | ||
if (injectedValue?.selectedElement.value === currentElement.value) { | ||
currentElement.value?.focus(); | ||
} | ||
}); | ||
function handleHover() { | ||
if (!props.disabled) { | ||
injectedValue!.changeSelected(currentElement.value!); | ||
} | ||
} | ||
function handleCloseMenu() { | ||
injectedValue?.hideTooltip(); | ||
document.querySelector("body")!.style.pointerEvents = ""; | ||
setTimeout(() => { | ||
injectedValue?.triggerElement.value?.focus(); | ||
}, 0); | ||
} | ||
function updateModelValue() { | ||
return emit("update:modelValue", !props.modelValue); | ||
} | ||
</script> | ||
|
||
<template> | ||
<div | ||
role="menuitem" | ||
ref="currentElement" | ||
@keydown="handleKeydown" | ||
data-radix-vue-collection-item | ||
@click.prevent="updateModelValue" | ||
@mouseenter="handleHover" | ||
@mouseleave="injectedValue!.changeSelected(null)" | ||
:data-highlighted=" | ||
injectedValue?.selectedElement.value === currentElement ? '' : null | ||
" | ||
:aria-disabled="props.disabled ? true : undefined" | ||
:data-disabled="props.disabled ? '' : undefined" | ||
:data-orientation="injectedValue?.orientation" | ||
:tabindex=" | ||
injectedValue?.selectedElement.value === currentElement ? '0' : '-1' | ||
" | ||
> | ||
<input | ||
type="checkbox" | ||
:id="props.id" | ||
:aria-valuenow="props.modelValue" | ||
v-bind="props.modelValue" | ||
@change="updateModelValue" | ||
:checked="props.modelValue" | ||
:name="props.name" | ||
aria-hidden="true" | ||
:disabled="props.disabled" | ||
style="opacity: 0; position: absolute; inset: 0" | ||
/> | ||
<slot /> | ||
</div> | ||
</template> |
Oops, something went wrong.