Skip to content

Commit

Permalink
implement OnyxMore component
Browse files Browse the repository at this point in the history
  • Loading branch information
larsrickert committed Oct 30, 2024
1 parent 7b9cba0 commit d845217
Show file tree
Hide file tree
Showing 7 changed files with 207 additions and 51 deletions.
24 changes: 24 additions & 0 deletions packages/sit-onyx/src/components/OnyxMore/OnyxMore.stories.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import type { Meta, StoryObj } from "@storybook/vue3";
import { h } from "vue";
import OnyxNavButton from "../OnyxNavBar/modules/OnyxNavButton/OnyxNavButton.vue";
import { NAV_BAR_BUTTONS_INJECTION_KEY } from "../OnyxNavBar/types";
import OnyxMore from "./OnyxMore.vue";

const meta: Meta<typeof OnyxMore> = {
title: "Support/More",
component: OnyxMore,
};

export default meta;
type Story = StoryObj<typeof OnyxMore>;

export const Default = {
args: {
is: "div",
injectionKey: NAV_BAR_BUTTONS_INJECTION_KEY,
default: Array.from({ length: 24 }, (_, index) =>
h(OnyxNavButton, { label: `Element ${index + 1}` }),
),
more: ({ hiddenElements }) => h("div", `+${hiddenElements.value.length} more`),
},
} satisfies Story;
53 changes: 53 additions & 0 deletions packages/sit-onyx/src/components/OnyxMore/OnyxMore.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
<script lang="ts" setup>
import { provide, reactive, ref, toRef, type Ref } from "vue";
import { useMore, type HTMLOrInstanceRef } from "../../composables/useMore";
import type { OnyxMoreProps } from "./types";
const props = defineProps<OnyxMoreProps>();
defineSlots<{
/**
* List of components to render. Each child must implement the `useMoreChild()` composable.
*/
default(): unknown;
/**
* Slot to display at the end if not all default slot elements fit in the available width.
*/
more(props: typeof more): unknown;
}>();
const parentRef = ref<HTMLOrInstanceRef>();
const componentRefs = reactive(new Map<string, Ref<HTMLOrInstanceRef>>());
const more = useMore({
parentRef,
componentRefs,
disabled: toRef(props, "disabled"),
});
// eslint-disable-next-line vue/no-setup-props-reactivity-loss
provide(props.injectionKey, {
components: componentRefs,
visibleComponents: more.visibleElements,
});
</script>

<template>
<component :is="props.is" ref="parentRef" class="onyx-more">
<slot></slot>
<slot v-if="more.hiddenElements.value.length > 0" name="more" v-bind="more"></slot>
</component>
</template>

<style lang="scss">
@use "../../styles/mixins/layers.scss";
.onyx-more {
@include layers.component() {
display: flex;
align-items: center;
gap: var(--onyx-spacing-4xs);
overflow-x: clip;
}
}
</style>
18 changes: 18 additions & 0 deletions packages/sit-onyx/src/components/OnyxMore/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import type { MoreInjectionKey } from "../../composables/useMore";

export type OnyxMoreProps = {
/**
* Component to render (e.g. `<ul>` or `<div>`).
*/
is: string;
/**
* Injection key to use. Must match the one used in the child components.
* Will not be reactive so it must not be changed.
*/
injectionKey: MoreInjectionKey;
/**
* Whether the intersection observer should be disabled (e.g. when more feature is currently not needed due to mobile layout).
* Can increase performance.
*/
disabled?: boolean;
};
27 changes: 9 additions & 18 deletions packages/sit-onyx/src/components/OnyxNavBar/OnyxNavBar.vue
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,13 @@ import { createNavigationMenu } from "@sit-onyx/headless";
import chevronLeftSmall from "@sit-onyx/icons/chevron-left-small.svg?raw";
import menu from "@sit-onyx/icons/menu.svg?raw";
import moreVertical from "@sit-onyx/icons/more-vertical.svg?raw";
import { computed, provide, reactive, ref, toRef, unref, type Ref } from "vue";
import { useMore, type HTMLOrInstanceRef } from "../../composables/useMore";
import { computed, provide, ref, toRef } from "vue";
import { useResizeObserver } from "../../composables/useResizeObserver";
import { injectI18n } from "../../i18n";
import { ONYX_BREAKPOINTS } from "../../types";
import OnyxIconButton from "../OnyxIconButton/OnyxIconButton.vue";
import OnyxMobileNavButton from "../OnyxMobileNavButton/OnyxMobileNavButton.vue";
import OnyxMore from "../OnyxMore/OnyxMore.vue";
import OnyxNavAppArea from "../OnyxNavAppArea/OnyxNavAppArea.vue";
import {
MOBILE_NAV_BAR_INJECTION_KEY,
Expand Down Expand Up @@ -81,16 +81,6 @@ const isMobile = computed(() => {
provide(MOBILE_NAV_BAR_INJECTION_KEY, isMobile);
const menuBarRef = ref<HTMLElement>();
const navButtonRefs = reactive(new Map<string, Ref<HTMLOrInstanceRef>>());
provide(NAV_BAR_BUTTONS_INJECTION_KEY, navButtonRefs);
const { visibleElements, hiddenElements, totalElements } = useMore({
parentRef: menuBarRef,
componentRefs: computed(() => Array.from(navButtonRefs.values()).map((ref) => unref(ref))),
disabled: isMobile,
});
defineExpose({
/**
* Closes the mobile burger and context menu.
Expand All @@ -113,9 +103,6 @@ defineExpose({
</script>

<template>
<div>{{ visibleElements }} visible</div>
<div>{{ hiddenElements }} hidden</div>
<div>{{ totalElements }} total</div>
<header ref="navBarRef" class="onyx-nav-bar" :class="{ 'onyx-nav-bar--mobile': isMobile }">
<div class="onyx-nav-bar__content">
<span
Expand Down Expand Up @@ -166,9 +153,14 @@ defineExpose({
</OnyxMobileNavButton>

<nav v-else class="onyx-nav-bar__nav" v-bind="nav">
<ul ref="menuBarRef" role="menubar">
<OnyxMore
is="ul"
role="menubar"
:injection-key="NAV_BAR_BUTTONS_INJECTION_KEY"
:disabled="isMobile"
>
<slot></slot>
</ul>
</OnyxMore>
</nav>
</template>

Expand Down Expand Up @@ -252,7 +244,6 @@ $gap: var(--onyx-spacing-md);
&__nav {
grid-area: nav;
overflow-x: clip;
> ul {
display: flex;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@
import chevronRightSmall from "@sit-onyx/icons/chevron-right-small.svg?raw";
import { computed, inject, toRef } from "vue";
import { MANAGED_SYMBOL, useManagedState } from "../../../../composables/useManagedState";
import { useMoreChild } from "../../../../composables/useMore";
import OnyxExternalLinkIcon from "../../../OnyxExternalLinkIcon/OnyxExternalLinkIcon.vue";
import OnyxIcon from "../../../OnyxIcon/OnyxIcon.vue";
import { MOBILE_NAV_BAR_INJECTION_KEY } from "../../types";
import { useNavBarButtons } from "../../useNavBarButtons";
import { MOBILE_NAV_BAR_INJECTION_KEY, NAV_BAR_BUTTONS_INJECTION_KEY } from "../../types";
import NavButtonLayout from "./NavButtonLayout.vue";
import type { OnyxNavButtonProps } from "./types";
Expand Down Expand Up @@ -39,7 +39,7 @@ const slots = defineSlots<{
const isMobile = inject(MOBILE_NAV_BAR_INJECTION_KEY);
const hasChildren = computed(() => !!slots.children);
const { navButtonRef } = useNavBarButtons();
const { componentRef, isVisible } = useMoreChild(NAV_BAR_BUTTONS_INJECTION_KEY);
const { state: mobileChildrenOpen } = useManagedState(
toRef(() => props.mobileChildrenOpen),
Expand All @@ -58,13 +58,14 @@ const handleParentClick = (event: MouseEvent) => {

<template>
<NavButtonLayout
ref="navButtonRef"
ref="componentRef"
v-bind="props"
v-model:mobile-children-open="mobileChildrenOpen"
class="onyx-nav-button"
:class="{
'onyx-nav-button--mobile': isMobile,
'onyx-nav-button--active': props.active,
'onyx-nav-button--hidden': !isMobile && !isVisible,
}"
:is-mobile="isMobile ?? false"
>
Expand Down Expand Up @@ -108,6 +109,10 @@ $border-radius: var(--onyx-radius-sm);
$gap: var(--onyx-spacing-2xs);
list-style: none;
&--hidden {
visibility: hidden;
}
&__trigger {
display: inline-flex;
position: relative;
Expand Down
8 changes: 3 additions & 5 deletions packages/sit-onyx/src/components/OnyxNavBar/types.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { ComputedRef, InjectionKey, Ref } from "vue";
import type { HTMLOrInstanceRef } from "../../composables/useMore";
import type { ComputedRef, InjectionKey } from "vue";
import type { MoreInjectionKey } from "../../composables/useMore";
import type { OnyxBreakpoint } from "../../types";
import type { OnyxNavAppAreaProps } from "../OnyxNavAppArea/types";

Expand Down Expand Up @@ -31,6 +31,4 @@ export type OnyxNavBarProps = Omit<OnyxNavAppAreaProps, "label"> & {
*/
export const MOBILE_NAV_BAR_INJECTION_KEY = Symbol() as InjectionKey<ComputedRef<boolean>>;

export const NAV_BAR_BUTTONS_INJECTION_KEY = Symbol() as InjectionKey<
Map<string, Ref<HTMLOrInstanceRef>>
>;
export const NAV_BAR_BUTTONS_INJECTION_KEY = Symbol() as MoreInjectionKey;
Loading

0 comments on commit d845217

Please sign in to comment.