Skip to content

Commit

Permalink
feat(dropdown): add modalDesktop property
Browse files Browse the repository at this point in the history
  • Loading branch information
mlmoravek committed Sep 25, 2024
1 parent 80a0c5c commit 96c1d21
Show file tree
Hide file tree
Showing 4 changed files with 54 additions and 48 deletions.
1 change: 1 addition & 0 deletions packages/docs/components/Dropdown.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ sidebarDepth: 2
| checkScroll | | | - | <div><small>From <b>config</b>:</small></div><code style='white-space: nowrap; padding: 0;'>dropdown: {<br>&nbsp;&nbsp;checkScroll: false<br>}</code> |
| closeable | | | - | <div><small>From <b>config</b>:</small></div><code style='white-space: nowrap; padding: 0;'>dropdown: {<br>&nbsp;&nbsp;closeable: ["escape","outside","content"]<br>}</code> |
| delay | | | - | |
| desktopModal | | | - | <div><small>From <b>config</b>:</small></div><code style='white-space: nowrap; padding: 0;'>dropdown: {<br>&nbsp;&nbsp;desktopModal: false<br>}</code> |
| disabled | | | - | <code style='white-space: nowrap; padding: 0;'>false</code> |
| expanded | | | - | <code style='white-space: nowrap; padding: 0;'>false</code> |
| inline | | | - | <code style='white-space: nowrap; padding: 0;'>false</code> |
Expand Down
87 changes: 44 additions & 43 deletions packages/oruga/src/components/dropdown/Dropdown.vue
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
ref,
watch,
onUnmounted,
useTemplateRef,
type Component,
} from "vue";
Expand Down Expand Up @@ -58,7 +59,6 @@ const props = withDefaults(defineProps<DropdownProps<T, IsMultiple>>(), {
scrollable: false,
maxHeight: () => getOption("dropdown.maxHeight", 200),
position: () => getOption("dropdown.position", "bottom-left"),
mobileModal: () => getOption("dropdown.mobileModal", true),
animation: () => getOption("dropdown.animation", "fade"),
trapFocus: () => getOption("dropdown.trapFocus", true),
checkScroll: () => getOption("dropdown.checkScroll", false),
Expand All @@ -73,6 +73,8 @@ const props = withDefaults(defineProps<DropdownProps<T, IsMultiple>>(), {
getOption("dropdown.closeable", ["escape", "outside", "content"]),
tabindex: 0,
ariaRole: () => getOption("dropdown.ariaRole", "list"),
desktopModal: () => getOption("dropdown.desktopModal", false),
mobileModal: () => getOption("dropdown.mobileModal", true),
mobileBreakpoint: () => getOption("dropdown.mobileBreakpoint"),
teleport: () => getOption("dropdown.teleport", false),
});
Expand Down Expand Up @@ -106,6 +108,9 @@ const emits = defineEmits<{
(e: "scroll-end"): void;
}>();
const contentRef = useTemplateRef<HTMLElement | Component>("contentRef");
const triggerRef = useTemplateRef<HTMLElement>("triggerRef");
/** The selected item value */
// const vmodel = defineModel<ModelValue>({ default: undefined });
const vmodel = useVModel<ModelValue>();
Expand All @@ -123,28 +128,28 @@ watch(
const { isMobile } = useMatchMedia(props.mobileBreakpoint);
// check if mobile modal should be shown
const isMobileModal = computed(
() => isMobile.value && props.mobileModal && !props.inline,
// check if should be shown as modal
const isModal = computed(
() =>
!props.inline &&
((isMobile.value && props.mobileModal) ||
(!isMobile.value && props.desktopModal)),
);
// check if client is mobile native
const isMobileNative = computed(() => props.mobileModal && isMobileAgent.any());
const isMobileNative = isClient && isMobileAgent.any();
const menuStyle = computed(() => ({
maxHeight: props.scrollable ? toCssDimension(props.maxHeight) : null,
overflow: props.scrollable ? "auto" : null,
}));
const hoverable = computed(() => props.triggers.indexOf("hover") >= 0);
const hoverable = computed(() => props.triggers.includes("hover"));
const toggleScroll = usePreventScrolling();
// --- Event Handler ---
const contentRef = ref<HTMLElement | Component>();
const triggerRef = ref<HTMLElement>();
const eventCleanups = [];
let timer: NodeJS.Timeout;
Expand All @@ -159,8 +164,8 @@ const cancelOptions = computed(() =>
watch(
isActive,
(value) => {
// on active set event handler
if (value && isClient) {
// on active set event handler if not open as modal
if (value && isClient && !isModal.value) {
if (cancelOptions.value.indexOf("outside") >= 0) {
// set outside handler
eventCleanups.push(
Expand Down Expand Up @@ -199,48 +204,51 @@ onUnmounted(() => {
/** Close dropdown if clicked outside. */
function onClickedOutside(): void {
if (!isActive.value || props.inline) return;
if (cancelOptions.value.indexOf("outside") < 0) return;
if (!cancelOptions.value.includes("outside")) return;
emits("close", "outside");
isActive.value = false;
}
/** Keypress event that is bound to the document */
function onKeyPress(event: KeyboardEvent): void {
if (isActive.value && (event.key === "Escape" || event.key === "Esc")) {
if (cancelOptions.value.indexOf("escape") < 0) return;
if (!cancelOptions.value.includes("escape")) return;
emits("close", "escape");
isActive.value = false;
}
}
function onClick(): void {
if (props.triggers.indexOf("click") < 0) return;
// check if is mobile native and hoverable together
if (isMobileNative && hoverable.value) toggle();
// check normal click conditions
if (!props.triggers.includes("click")) return;
toggle();
}
function onContextMenu(event: MouseEvent): void {
if (props.triggers.indexOf("contextmenu") < 0) return;
if (!props.triggers.includes("contextmenu")) return;
event.preventDefault();
open();
}
function onFocus(): void {
if (props.triggers.indexOf("focus") < 0) return;
if (!props.triggers.includes("focus")) return;
open();
}
const isHovered = ref(false);
function onHover(): void {
if (!isMobileNative.value && props.triggers.indexOf("hover") >= 0) {
isHovered.value = true;
open();
}
if (isMobileNative) return;
if (!props.triggers.includes("hover")) return;
isHovered.value = true;
open();
}
function onHoverLeave(): void {
if (!isMobileNative.value && isHovered.value) {
isHovered.value = false;
onClose();
}
if (isMobileNative) return;
if (!isHovered.value) return;
isHovered.value = false;
onClose();
}
/** Toggle dropdown if it's not disabled. */
Expand All @@ -265,7 +273,7 @@ function open(): void {
}
function onClose(): void {
if (cancelOptions.value.indexOf("content") < 0) return;
if (!cancelOptions.value.includes("content")) return;
emits("close", "content");
isActive.value = !props.closeable;
if (timer && props.closeable) clearTimeout(timer);
Expand Down Expand Up @@ -350,12 +358,9 @@ const rootClasses = defineClasses(
["disabledClass", "o-drop--disabled", null, computed(() => props.disabled)],
["expandedClass", "o-drop--expanded", null, computed(() => props.expanded)],
["inlineClass", "o-drop--inline", null, computed(() => props.inline)],
[
"mobileClass",
"o-drop--mobile",
null,
computed(() => isMobileModal.value && !hoverable.value),
],
["mobileClass", "o-drop--mobile", null, isMobile],
["modalClass", "o-drop--modal", null, isModal],
["hoverableClass", "o-drop--hoverable", null, hoverable],
[
"positionClass",
"o-drop--position-",
Expand All @@ -368,22 +373,18 @@ const rootClasses = defineClasses(
null,
computed(() => isActive.value || props.inline),
],
["hoverableClass", "o-drop--hoverable", null, hoverable],
);
const triggerClasses = defineClasses(["triggerClass", "o-drop__trigger"]);
const positionWrapperClasses = defineClasses([
const teleportClasses = defineClasses([
"teleportClass",
"o-drop--teleport",
null,
computed(() => !!props.teleport),
]);
const menuMobileOverlayClasses = defineClasses([
"menuMobileOverlayClass",
"o-drop__overlay",
]);
const overlayClasses = defineClasses(["overlayClass", "o-drop__overlay"]);
const menuClasses = defineClasses(
["menuClass", "o-drop__menu"],
Expand Down Expand Up @@ -437,18 +438,18 @@ defineExpose({ $trigger: triggerRef, $content: contentRef, value: vmodel });
v-slot="{ setContent }"
v-model:position="autoPosition"
:teleport="teleport"
:class="[...rootClasses, ...positionWrapperClasses]"
:class="[...rootClasses, ...teleportClasses]"
:trigger="triggerRef"
:disabled="!isActive"
default-position="bottom"
:disable-positioning="!isMobileModal">
:disable-positioning="!isModal">
<transition :name="animation">
<div
v-if="isMobileModal"
v-if="isModal"
v-show="isActive"
:tabindex="-1"
:class="menuMobileOverlayClasses"
:aria-hidden="disabled || !isActive" />
:class="overlayClasses"
tabindex="-1"
@click="onClickedOutside" />
</transition>

<transition :name="animation">
Expand Down
8 changes: 6 additions & 2 deletions packages/oruga/src/components/dropdown/props.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ export type DropdownProps<
| "bottom-right";
/** Dropdown content (items) are shown into a modal on mobile */
mobileModal?: boolean;
/** Dropdown content (items) are shown into a modal on desktop */
desktopModal?: boolean;
/** Custom animation (transition name) */
animation?: string;
/** Trap focus inside the dropdown. */
Expand Down Expand Up @@ -94,8 +96,6 @@ type DropdownClasses = Partial<{
triggerClass: ComponentClass;
/** Class of dropdown menu when inline */
inlineClass: ComponentClass;
/** Class of the overlay when on mobile */
menuMobileOverlayClass: ComponentClass;
/** Class of the dropdown menu */
menuClass: ComponentClass;
/** Class of dropdown menu position */
Expand All @@ -104,6 +104,10 @@ type DropdownClasses = Partial<{
menuActiveClass: ComponentClass;
/** Class of dropdown when on mobile */
mobileClass: ComponentClass;
/** Class of dropdown when on is shown as modal */
modalClass: ComponentClass;
/** Class of the overlay when is shown as modal */
overlayClass: ComponentClass;
/** Class of dropdown when disabled */
disabledClass: ComponentClass;
/** Class of dropdown when expanded */
Expand Down
6 changes: 3 additions & 3 deletions packages/oruga/src/components/modal/Modal.vue
Original file line number Diff line number Diff line change
Expand Up @@ -249,7 +249,7 @@ if (isClient) {
if (!props.overlay)
// register outside click event listener when is active
useClickOutside(contentRef, clickedOutside, {
useClickOutside(contentRef, onClickedOutside, {
trigger: isActive,
});
}
Expand All @@ -261,7 +261,7 @@ function onKeyPress(event: KeyboardEvent): void {
}
/** Close fixed sidebar if clicked outside. */
function clickedOutside(event: Event): void {
function onClickedOutside(event: Event): void {
if (!isActive.value || isAnimating.value) return;
if (props.overlay || !event.composedPath().includes(contentRef.value))
event.preventDefault();
Expand Down Expand Up @@ -353,7 +353,7 @@ defineExpose({ close });
v-if="overlay"
:class="overlayClasses"
tabindex="-1"
@click="clickedOutside" />
@click="onClickedOutside" />

<div
ref="contentRef"
Expand Down

0 comments on commit 96c1d21

Please sign in to comment.