mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-03-09 15:10:17 +00:00
App mode mobile redesign (#9047)
Reworks the app mode display for mobile devices. Adds multiple bottom tabs that can be swiped between.  To be handled in followup PRs - Nicer error display - Support for even smaller screens - UX improvements for the 'Outputs' pane - Was postponed to minimize conflicts with non-mobile development. ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-9047-App-mode-mobile-redesign-30e6d73d365081388e4adea4df886522) by [Unito](https://www.unito.io) --------- Co-authored-by: GitHub Action <action@github.com>
This commit is contained in:
65
src/components/common/DropdownItem.vue
Normal file
65
src/components/common/DropdownItem.vue
Normal file
@@ -0,0 +1,65 @@
|
||||
<script setup lang="ts">
|
||||
import type { MenuItem } from 'primevue/menuitem'
|
||||
import {
|
||||
DropdownMenuItem,
|
||||
DropdownMenuPortal,
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuSub,
|
||||
DropdownMenuSubContent,
|
||||
DropdownMenuSubTrigger
|
||||
} from 'reka-ui'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { toValue } from 'vue'
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
defineOptions({
|
||||
inheritAttrs: false
|
||||
})
|
||||
|
||||
defineProps<{ itemClass: string; contentClass: string; item: MenuItem }>()
|
||||
</script>
|
||||
<template>
|
||||
<DropdownMenuSeparator
|
||||
v-if="item.separator"
|
||||
class="h-[1px] bg-border-subtle m-1"
|
||||
/>
|
||||
<DropdownMenuSub v-else-if="item.items">
|
||||
<DropdownMenuSubTrigger
|
||||
:class="itemClass"
|
||||
:disabled="toValue(item.disabled) ?? !item.items?.length"
|
||||
>
|
||||
{{ item.label }}
|
||||
<i class="ml-auto icon-[lucide--chevron-right]" />
|
||||
</DropdownMenuSubTrigger>
|
||||
<DropdownMenuPortal>
|
||||
<DropdownMenuSubContent
|
||||
:class="contentClass"
|
||||
:side-offset="2"
|
||||
:align-offset="-5"
|
||||
>
|
||||
<DropdownItem
|
||||
v-for="(subitem, index) in item.items"
|
||||
:key="toValue(subitem.label) ?? index"
|
||||
:item="subitem"
|
||||
:item-class
|
||||
:content-class
|
||||
/>
|
||||
</DropdownMenuSubContent>
|
||||
</DropdownMenuPortal>
|
||||
</DropdownMenuSub>
|
||||
<DropdownMenuItem
|
||||
v-else
|
||||
:class="itemClass"
|
||||
:disabled="toValue(item.disabled) ?? !item.command"
|
||||
@select="item.command?.({ originalEvent: $event, item })"
|
||||
>
|
||||
<i class="size-5" :class="item.icon" />
|
||||
{{ item.label }}
|
||||
<div
|
||||
v-if="item.new"
|
||||
class="ml-auto bg-primary-background rounded-full text-xxs font-bold px-1 flex leading-none items-center"
|
||||
v-text="t('contextMenu.new')"
|
||||
/>
|
||||
</DropdownMenuItem>
|
||||
</template>
|
||||
74
src/components/common/DropdownMenu.vue
Normal file
74
src/components/common/DropdownMenu.vue
Normal file
@@ -0,0 +1,74 @@
|
||||
<script setup lang="ts">
|
||||
import type { MenuItem } from 'primevue/menuitem'
|
||||
import {
|
||||
DropdownMenuArrow,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuPortal,
|
||||
DropdownMenuRoot,
|
||||
DropdownMenuTrigger
|
||||
} from 'reka-ui'
|
||||
import { computed, toValue } from 'vue'
|
||||
|
||||
import DropdownItem from '@/components/common/DropdownItem.vue'
|
||||
import Button from '@/components/ui/button/Button.vue'
|
||||
import { cn } from '@/utils/tailwindUtil'
|
||||
|
||||
defineOptions({
|
||||
inheritAttrs: false
|
||||
})
|
||||
|
||||
const { itemClass: itemProp, contentClass: contentProp } = defineProps<{
|
||||
entries?: MenuItem[]
|
||||
icon?: string
|
||||
to?: string | HTMLElement
|
||||
itemClass?: string
|
||||
contentClass?: string
|
||||
}>()
|
||||
|
||||
const itemClass = computed(() =>
|
||||
cn(
|
||||
'data-[highlighted]:bg-secondary-background-hover data-[disabled]:pointer-events-none data-[disabled]:text-muted-foreground flex p-2 leading-none rounded-lg gap-1 cursor-pointer m-1',
|
||||
itemProp
|
||||
)
|
||||
)
|
||||
|
||||
const contentClass = computed(() =>
|
||||
cn(
|
||||
'z-1700 rounded-lg p-2 bg-base-background border border-border-subtle min-w-[220px] shadow-sm will-change-[opacity,transform] data-[side=top]:animate-slideDownAndFade data-[side=right]:animate-slideLeftAndFade data-[side=bottom]:animate-slideUpAndFade data-[side=left]:animate-slideRightAndFade',
|
||||
contentProp
|
||||
)
|
||||
)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<DropdownMenuRoot>
|
||||
<DropdownMenuTrigger as-child>
|
||||
<slot name="button">
|
||||
<Button size="icon">
|
||||
<i :class="icon ?? 'icon-[lucide--menu]'" />
|
||||
</Button>
|
||||
</slot>
|
||||
</DropdownMenuTrigger>
|
||||
|
||||
<DropdownMenuPortal :to>
|
||||
<DropdownMenuContent
|
||||
side="bottom"
|
||||
:side-offset="5"
|
||||
:collision-padding="10"
|
||||
v-bind="$attrs"
|
||||
:class="contentClass"
|
||||
>
|
||||
<slot :item-class>
|
||||
<DropdownItem
|
||||
v-for="(item, index) in entries ?? []"
|
||||
:key="toValue(item.label) ?? index"
|
||||
:item-class
|
||||
:content-class
|
||||
:item
|
||||
/>
|
||||
</slot>
|
||||
<DropdownMenuArrow class="fill-base-background stroke-border-subtle" />
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenuPortal>
|
||||
</DropdownMenuRoot>
|
||||
</template>
|
||||
@@ -8,7 +8,7 @@
|
||||
v-if="!hideButtons"
|
||||
:aria-label="t('g.decrement')"
|
||||
data-testid="decrement"
|
||||
class="h-full w-8 rounded-r-none hover:bg-base-foreground/20 disabled:opacity-30"
|
||||
class="h-full aspect-8/7 rounded-r-none hover:bg-base-foreground/20 disabled:opacity-30"
|
||||
variant="muted-textonly"
|
||||
:disabled="!canDecrement"
|
||||
tabindex="-1"
|
||||
@@ -53,7 +53,7 @@
|
||||
v-if="!hideButtons"
|
||||
:aria-label="t('g.increment')"
|
||||
data-testid="increment"
|
||||
class="h-full w-8 rounded-l-none hover:bg-base-foreground/20 disabled:opacity-30"
|
||||
class="h-full aspect-8/7 rounded-l-none hover:bg-base-foreground/20 disabled:opacity-30"
|
||||
variant="muted-textonly"
|
||||
:disabled="!canIncrement"
|
||||
tabindex="-1"
|
||||
|
||||
Reference in New Issue
Block a user