Skip to content

Commit

Permalink
feat: add new LSB navigation components and router item state managem…
Browse files Browse the repository at this point in the history
…ent (#5104)

* feat: add new LSB navigation components and router item state management

Signed-off-by: Wanjin Noh <[email protected]>

* build(pinia): update pinia to version 2.2.7

Signed-off-by: Wanjin Noh <[email protected]>

---------

Signed-off-by: Wanjin Noh <[email protected]>
  • Loading branch information
WANZARGEN authored Nov 28, 2024
1 parent 97c84c5 commit 7ac3d89
Show file tree
Hide file tree
Showing 9 changed files with 369 additions and 125 deletions.
6 changes: 3 additions & 3 deletions apps/web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -62,13 +62,14 @@
"keycloak-js": "^10.0.2",
"lodash": "^4.17.21",
"numeral": "^2.0.6",
"pinia": "^2.0.28",
"pinia": "^2.2.7",
"portal-vue": "^2.1.7",
"process": "^0.11.10",
"prosemirror-model": "^1.19.2",
"prosemirror-state": "^1.4.3",
"prosemirror-transform": "^1.7.3",
"prosemirror-view": "^1.31.5",
"qrcode": "^1.5.4",
"uuid": "^8.3.2",
"v-click-outside": "^3.0.1",
"v-tooltip": "^2.0.3",
Expand All @@ -81,8 +82,7 @@
"vue-selecto": "^1.6.4",
"vuedraggable": "^2.24.3",
"vuex": "^3.6.2",
"webfontloader": "^1.6.28",
"qrcode": "^1.5.4"
"webfontloader": "^1.6.28"
},
"devDependencies": {
"@babel/preset-env": "^7.21.4",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<template>
<aside>
<div class="pt-4 pb-8 px-2">
<slot />
</div>
</aside>
</template>

Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<script setup lang="ts">
import { PDivider } from '@cloudforet/mirinae';
</script>

<template>
<div class="pt-1 pb-3 px-2 my-2">
<p-divider />
</div>
</template>
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
<script setup lang="ts">
import type { TranslateResult } from 'vue-i18n';
import type { Location } from 'vue-router';
import { PLazyImg, PI } from '@cloudforet/mirinae';
import { useLsbRouterItemState } from '@/common/modules/navigations/new-lsb/composables/use-lsb-router-item-state';
import type { LSBIcon } from '@/common/modules/navigations/new-lsb/type';
const props = defineProps<{
label?: TranslateResult;
icon?: LSBIcon;
imgIcon?: string;
to?: Location;
currentPath?: string;
openNewTab?: boolean;
}>();
const {
isSelected,
iconName,
iconColor,
imgIconUrl,
} = useLsbRouterItemState(props);
</script>

<template>
<router-link ref="itemEl"
class="lsb-router-button inline-flex items-center w-full h-8 justify-between px-2 border border-transparent outline-none rounded-md text-gray-800"
:class="[{'selected': isSelected}]"
:target="props.openNewTab ? '_blank' : '_self'"
:to="props.to ? props.to : {}"
@click.native="$event.stopImmediatePropagation()"
>
<slot name="before-text"
v-bind="props"
/>
<div ref="textEl"
class="inline-flex items-center gap-1 overflow-hidden whitespace-no-wrap"
>
<p-i v-if="props.icon"
:name="iconName"
:color="iconColor"
width="1rem"
height="1rem"
class="flex-shrink-0"
/>
<p-lazy-img
v-if="props.imgIcon"
:src="imgIconUrl"
width="1rem"
height="1rem"
class="flex-shrink-0"
/>
<span class="text-label-md">
<slot>
{{ props.label }}
</slot>
</span>
<slot name="after-text"
v-bind="props"
/>
</div>
<slot name="right-extra"
v-bind="props"
/>
</router-link>
</template>

<style scoped lang="postcss">
.lsb-router-button {
&:focus, &:focus-within, &:active {
@apply bg-white border-secondary1;
box-shadow: 0 0 0 2px rgba(theme('colors.secondary1'), 0.2);
}
&.selected {
@apply bg-blue-200;
}
&:hover {
@apply bg-blue-100 cursor-pointer;
}
}
</style>
151 changes: 151 additions & 0 deletions apps/web/src/common/modules/navigations/new-lsb/LSBRouterItem.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
<script setup lang="ts">
import { useElementSize } from '@vueuse/core';
import type { Ref } from 'vue';
import {
computed, reactive, ref,
} from 'vue';
import type { TranslateResult } from 'vue-i18n';
import type { Location } from 'vue-router';
import { PI, PLazyImg, PTooltip } from '@cloudforet/mirinae';
import type { MenuId } from '@/lib/menu/config';
import BetaMark from '@/common/components/marks/BetaMark.vue';
import NewMark from '@/common/components/marks/NewMark.vue';
import UpdateMark from '@/common/components/marks/UpdateMark.vue';
import FavoriteButton from '@/common/modules/favorites/favorite-button/FavoriteButton.vue';
import { FAVORITE_TYPE, type FavoriteOptions } from '@/common/modules/favorites/favorite-button/type';
import { useLsbRouterItemState } from '@/common/modules/navigations/new-lsb/composables/use-lsb-router-item-state';
import type { HighlightTagType, LSBIcon } from '@/common/modules/navigations/new-lsb/type';
interface Props {
id: string;
label?: TranslateResult;
icon?: LSBIcon;
imgIcon?: string;
to?: Location;
currentPath?: string;
openNewTab?: boolean;
highlightTag?: HighlightTagType;
favoriteOptions?: FavoriteOptions;
hideFavorite?: boolean;
isAdminMode?: boolean;
index?: number | string;
}
const props = defineProps<Props>();
const itemEl = ref<HTMLElement | null>(null);
const textEl = ref<HTMLElement | null>(null);
const state = reactive({
itemWidth: computed<Ref<number>>(() => useElementSize(itemEl.value).width),
textWidth: computed<Ref<number>>(() => useElementSize(textEl.value).width),
hoveredItem: '' as MenuId | string,
isEllipsis: computed<boolean>(() => state.hoveredItem === props.id && (state.itemWidth.value - 20 === state.textWidth.value)),
});
const getIsHovered = (itemId?: MenuId | string) => state.hoveredItem && state.hoveredItem === itemId;
const {
isSelected,
iconName,
iconColor,
imgIconUrl,
} = useLsbRouterItemState(props);
</script>

<template>
<router-link ref="itemEl"
class="lsb-router-item inline-flex items-center w-full h-8 justify-between px-2 border border-transparent outline-none rounded-md text-label-md text-gray-800"
:class="[{'selected': isSelected, 'is-hide-favorite': props.hideFavorite}]"
:target="props.openNewTab ? '_blank' : '_self'"
:to="props.to ? props.to : {}"
@click.native="$event.stopImmediatePropagation()"
@mouseenter.native="state.hoveredItem = props.id"
@mouseleave.native="state.hoveredItem = ''"
>
<slot name="before-text"
v-bind="props"
/>
<div ref="textEl"
class="inline-flex items-center overflow-hidden whitespace-no-wrap"
>
<p-i v-if="props.icon"
:name="iconName"
:color="iconColor"
width="1rem"
height="1rem"
class="flex-shrink-0 ml-1"
/>
<p-lazy-img
v-if="props.imgIcon"
:src="imgIconUrl"
width="1rem"
height="1rem"
class="flex-shrink-0 ml-1"
/>
<slot>
<div class="item-text overflow-hidden whitespace-no-wrap">
<p-tooltip v-if="state.isEllipsis"
position="bottom-start"
:contents="props.label"
>
{{ props.label }}
</p-tooltip>
<span v-else>{{ props.label }}</span>
</div>
</slot>
<slot name="after-text"
v-bind="props"
/>
<span class="mark-wrapper h-full">
<new-mark v-if="props.highlightTag === 'new'" />
<update-mark v-else-if="props.highlightTag === 'update'" />
<beta-mark v-else-if="props.highlightTag === 'beta'" />
</span>
</div>
<slot name="right-extra"
v-bind="props"
/>
<favorite-button
v-if="!hideFavorite && !isAdminMode"
:item-id="props.favoriteOptions?.id ? props.favoriteOptions.id : props.id"
:favorite-type="props.favoriteOptions?.type ? props.favoriteOptions.type : FAVORITE_TYPE.MENU"
:visible-active-case-only="!getIsHovered(props.id)"
scale="0.8"
class="flex-shrink-0 ml-1"
/>
</router-link>
</template>

<style lang="postcss" scoped>
.lsb-router-item {
&:focus, &:focus-within, &:active {
@apply bg-white border-secondary1;
box-shadow: 0 0 0 2px rgba(theme('colors.secondary1'), 0.2);
}
&.selected {
@apply bg-blue-200;
}
&.is-hide-favorite {
.favorite-button {
@apply hidden;
}
&:hover {
.favorite-button {
@apply block;
}
}
}
&:hover {
@apply bg-blue-100 cursor-pointer;
}
.item-text {
text-overflow: ellipsis;
}
.mark-wrapper {
margin-top: -0.25rem;
}
}
</style>
38 changes: 38 additions & 0 deletions apps/web/src/common/modules/navigations/new-lsb/LSBTopTitle.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
<script setup lang="ts">
import type { Location } from 'vue-router';
import {
PLazyImg,
} from '@cloudforet/mirinae';
import { assetUrlConverter } from '@/lib/helper/asset-helper';
const props = defineProps<{
label?: string;
icon?: string;
subText?: string;
visibleAddButton?: boolean;
addButtonLink?: Location;
}>();
</script>

<template>
<div class=" flex items-center gap-1 h-8 mt-1 pb-2 px-2 text-gray-800 font-bold text-label-md">
<div class="flex items-center h-6">
<p-lazy-img
v-if="props.icon"
:src="assetUrlConverter(props.icon)"
width="1.5rem"
height="1.5rem"
class="icon rounded flex-shrink-0"
/>
<span>
<slot>{{ props.label }}</slot>
<span v-if="props.subText"
class="font-normal"
>{{ props.subText }}</span>
</span>
</div>
</div>
</template>
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import type { UnwrapRef, Ref } from 'vue';
import { computed } from 'vue';
import type { Location } from 'vue-router';
import { useRouter } from 'vue-router/composables';

import { assetUrlConverter } from '@/lib/helper/asset-helper';

import type { LSBIcon } from '@/common/modules/navigations/new-lsb/type';

interface UseLsbRouterItemProps {
to?: Ref<Readonly<Location>>;
currentPath?: Ref<Readonly<string>>;
icon?: Ref<Readonly<LSBIcon>>;
imgIcon?: Ref<Readonly<string>>;
}
export const useLsbRouterItemState = (props: UnwrapRef<UseLsbRouterItemProps>) => {
const router = useRouter();

const isSelected = computed<boolean>(() => {
let currentPath = props.currentPath;
if (!currentPath || !props.to) return false;

const resolved = router.resolve(props.to);
if (!resolved) return false;

if (currentPath.indexOf('?') > 0) {
currentPath = currentPath.slice(0, currentPath.indexOf('?'));
}
const resolvedHref = resolved.href;
return currentPath === resolvedHref;
});
const iconName = computed<string>(() => {
if (!props.icon) return '';
if (typeof props.icon === 'string') return props.icon;
return props.icon.name;
});
const iconColor = computed<string>(() => {
if (!props.icon) return 'inherit';
if (typeof props.icon === 'string') return 'inherit';
return props.icon.color || 'inherit';
});
const imgIconUrl = computed<string>(() => (props.imgIcon ? assetUrlConverter(props.imgIcon) : ''));

return {
isSelected,
iconName,
iconColor,
imgIconUrl,
};
};
2 changes: 2 additions & 0 deletions apps/web/src/common/modules/navigations/new-lsb/type.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export type LSBIcon = string | { name: string; color?: string; };
export type HighlightTagType = 'new' | 'beta' | 'update';
Loading

0 comments on commit 7ac3d89

Please sign in to comment.