-
Notifications
You must be signed in to change notification settings - Fork 254
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat(web-core): implement Fullscreenable component #7663
base: master
Are you sure you want to change the base?
Conversation
185481d
to
410ed96
Compare
410ed96
to
7e29bc3
Compare
ee0709f
to
0b4a00a
Compare
@xen-orchestra/web-core/lib/components/fullscreenable/FullscreenableHeader.vue
Outdated
Show resolved
Hide resolved
@xen-orchestra/web-core/lib/components/fullscreenable/FullscreenableHeader.vue
Outdated
Show resolved
Hide resolved
@xen-orchestra/web-core/lib/components/fullscreenable/FullscreenableHeader.vue
Outdated
Show resolved
Hide resolved
@xen-orchestra/web-core/lib/components/fullscreenable/FullscreenableHeader.vue
Outdated
Show resolved
Hide resolved
@xen-orchestra/web-core/lib/components/fullscreenable/FullscreenableHeader.vue
Outdated
Show resolved
Hide resolved
defineProps<{ | ||
back: () => void | ||
}>() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd use events instead:
defineProps<{ | |
back: () => void | |
}>() | |
const emit = defineEmits<{ | |
close: [] | |
}>() |
Then replace @click="back"
with @click="emit('close')"
above.
<template> | ||
<Teleport to="body" :disabled="!isMobile"> | ||
<div v-if="isOpen && isMobile" class="mobile"> | ||
<FullscreenableHeader v-if="isMobile" :back="close"><slot name="header" /></FullscreenableHeader> | ||
<slot /> | ||
</div> | ||
<slot v-else /> | ||
</Teleport> | ||
</template> | ||
|
||
<script lang="ts" setup> | ||
import FullscreenableHeader from '@core/components/fullscreenable/FullscreenableHeader.vue' | ||
import { ref, inject } from 'vue' | ||
import { useUiStore } from '@core/stores/ui.store' | ||
import { storeToRefs } from 'pinia' | ||
import { IK_FULLSCREENABLE_BEFORE_CLOSE } from '@core/utils/injection-keys.util' | ||
import { noop } from '@vueuse/core' | ||
|
||
const { isMobile } = storeToRefs(useUiStore()) | ||
|
||
const beforeClose = inject(IK_FULLSCREENABLE_BEFORE_CLOSE, noop) | ||
|
||
const isOpen = ref(true) | ||
const close = () => { | ||
beforeClose() | ||
isOpen.value = false | ||
} | ||
</script> | ||
|
||
<style lang="postcss" scoped> | ||
.mobile { | ||
background-color: var(--background-color-secondary); | ||
height: 100vh; | ||
position: absolute; | ||
top: 0; | ||
width: 100%; | ||
z-index: 10001; | ||
} | ||
</style> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think that this component should not be responsible of its state and thus, not to contain isOpen
/close
.
IMO, it should only be a wrapper to display an element in fullscreen on mobile, with a disabled
prop to handle case where we don't want to teleport the content.
Maybe it should be named accordingly to that statement (for example MobileFullscreen
. In my next comments, I will use this name for clarity, but it can be changed if we think about a better name)
Here is some suggestions (it's just a mock, there is missing imports, non-existant components etc.), the comments are here for explanation and can be removed:
<template> | |
<Teleport to="body" :disabled="!isMobile"> | |
<div v-if="isOpen && isMobile" class="mobile"> | |
<FullscreenableHeader v-if="isMobile" :back="close"><slot name="header" /></FullscreenableHeader> | |
<slot /> | |
</div> | |
<slot v-else /> | |
</Teleport> | |
</template> | |
<script lang="ts" setup> | |
import FullscreenableHeader from '@core/components/fullscreenable/FullscreenableHeader.vue' | |
import { ref, inject } from 'vue' | |
import { useUiStore } from '@core/stores/ui.store' | |
import { storeToRefs } from 'pinia' | |
import { IK_FULLSCREENABLE_BEFORE_CLOSE } from '@core/utils/injection-keys.util' | |
import { noop } from '@vueuse/core' | |
const { isMobile } = storeToRefs(useUiStore()) | |
const beforeClose = inject(IK_FULLSCREENABLE_BEFORE_CLOSE, noop) | |
const isOpen = ref(true) | |
const close = () => { | |
beforeClose() | |
isOpen.value = false | |
} | |
</script> | |
<style lang="postcss" scoped> | |
.mobile { | |
background-color: var(--background-color-secondary); | |
height: 100vh; | |
position: absolute; | |
top: 0; | |
width: 100%; | |
z-index: 10001; | |
} | |
</style> | |
<template> | |
<!-- When disabled or viewed on desktop, display the slot as is --> | |
<slot v-if="uiStore.isDesktop || disabled"> | |
<!-- In other cases, if this "panel" is `open`, let's display it in the body --> | |
<Teleport v-if="open" to="body"> | |
<div class="mobile-fullscreen"> | |
<!-- MobileHeadBar is a designed component in Figma --> | |
<!-- It's just used here as example and doesn't have to be created now --> | |
<MobileHeadBar> | |
<ButtonIcon :icon="faAngleLeft" @click="emit('close')" /> | |
<slot name="header" /> | |
</MobileHeadBar> | |
<slot /> | |
</div> | |
</Teleport> | |
</template> | |
<script lang="ts" setup> | |
import { ref, inject } from 'vue' | |
import { useUiStore } from '@core/stores/ui.store' | |
import { storeToRefs } from 'pinia' | |
defineProps<{ | |
disabled?: boolean // To display the slot as-is | |
open?: boolean // On mobile, to know if the panel is open | |
}>() | |
const emit = defineEmits<{ | |
close: [] | |
}>() | |
const uiStore = useUiStore() | |
</script> | |
<style lang="postcss" scoped> | |
.mobile-fullscreen { | |
background-color: var(--background-color-secondary); | |
position: fixed; | |
inset: 0; | |
z-index: 100; | |
} | |
</style> |
See MenuList
below for usage suggestion.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I can't comment code which had not been modified so I'll comment here.
First, I don't think it should be the developer responsibility to remember to wrap each MenuList
in the MobileFullscreen
component each time it's used. This is likely to be error-prone and tedious.
IMO, MobileFullscreen
should be integrated directly in components, whenever it's necessary.
In the case of MenuList
I would replace:
<template>
<slot :is-open="isOpen" :open="open" name="trigger" />
<Teleport :disabled="!shouldTeleport" to="body">
<ul v-if="!hasTrigger || isOpen" ref="menu" :class="{ horizontal, shadow }" class="menu-list" v-bind="$attrs">
<slot />
</ul>
</Teleport>
</template>
With:
<template>
<slot :is-open="isOpen" :open="open" name="trigger" />
<!-- We want do disable the wrapper if there is no trigger -->
<!-- Because No trigger = Menu displayed in the flow, not on action -->
<MobileFullscreen
:disabled="!hasTrigger"
:open="isOpen /* On mobile, the wrapper is open if the menu is open */"
@close="isOpen = false"
>
<!-- Keep the default behavior for desktop -->
<ul
v-if="!hasTrigger || isOpen"
ref="menu"
:class="{ horizontal, shadow, mobile: uiStore.isMobile }"
class="menu-list"
v-bind="$attrs"
>
<slot />
</ul>
</MobileFullscreen>
</template>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
With this solution, the Menu can be build normally:
<MenuList>
<template #trigger="{ open }">
<UiButton @click="open">Open</UiButton>
</template>
<MenuItem>Foo</MenuItem>
<MenuItem>Bar</MenuItem>
<MenuItem>
Baz
<template #submenu>
<MenuItem>Bar 1.1</MenuItem>
<MenuItem>
Bar 1.2
<template #submenu>
<MenuItem>Bar 1.2.1</MenuItem>
<MenuItem>Bar 1.2.2</MenuItem>
<MenuItem>Bar 1.2.3</MenuItem>
</template>
</MenuItem>
<MenuItem>Bar 1.3</MenuItem>
</template>
</MenuItem>
</MenuList>
When clicking the trigger, the menu will then be displayed as a dropdown on desktop and fullscreen on mobile.
If we remove the #trigger
template, the menu will be displayed as-is on desktop and mobile, but clicking on a menu item with submenu, then the submenu will be displayed as dropdown on desktop and fullscreen in mobile.
It should also handle multiple submenu correctly on mobile.
15e9740
to
bfedf66
Compare
9349b03
to
dc796f5
Compare
dc796f5
to
4a1758b
Compare
@xen-orchestra/web-core/lib/components/mobile-fullscreen/MobileFullscreen.vue
Outdated
Show resolved
Hide resolved
Co-authored-by: OlivierFL <66562640+OlivierFL@users.noreply.github.com>
Co-authored-by: OlivierFL <66562640+OlivierFL@users.noreply.github.com>
…eFullscreen.vue Co-authored-by: OlivierFL <66562640+OlivierFL@users.noreply.github.com>
Do not squash, review by commits
GIF
Checklist
Fixes #007
,See xoa-support#42
,See https://...
)Introduced by
CHANGELOG.unreleased.md