Skip to content
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

Implement "OnyxMoreList" component #986

Closed
9 tasks done
MajaZarkova opened this issue Apr 22, 2024 · 2 comments
Closed
9 tasks done

Implement "OnyxMoreList" component #986

MajaZarkova opened this issue Apr 22, 2024 · 2 comments
Assignees
Labels
dev Requires technical expertise

Comments

@MajaZarkova
Copy link
Contributor

MajaZarkova commented Apr 22, 2024

Depends on

Why?

This component should be used inside the navigationBar. While resizing the navItems should be grouped inside one flyout.
Therefore, we want to provide a generic "OnyxMoreList" component, where any list of components can be used to show any "more indicator" component.

Acceptance criteria

  • should show all elements, that fully fit into the available width
  • remaining items are hidden and a more indicator is shown (e.g. +3 more)
  • when resizing, the elements visibility is re-checked and updated if needed

Definition of Done

Approval

@MajaZarkova MajaZarkova added this to onyx Apr 22, 2024
@MajaZarkova MajaZarkova converted this from a draft issue Apr 22, 2024
@MajaZarkova MajaZarkova added the dev Requires technical expertise label Apr 22, 2024
@MajaZarkova MajaZarkova self-assigned this Apr 22, 2024
@MajaZarkova MajaZarkova added this to the Navigation bars milestone Apr 22, 2024
@MajaZarkova MajaZarkova moved this from New to Backlog in onyx Apr 22, 2024
@MajaZarkova MajaZarkova mentioned this issue Apr 22, 2024
13 tasks
@larsrickert
Copy link
Collaborator

Reference implementation for a chip list that we can re-use:

<script lang="ts" setup>
import { ScuChip } from '@scu/vue';
import { useIntersectionObserver } from '@vueuse/core';
import { computed, onBeforeUnmount, ref, watchEffect } from 'vue';

export interface Chip {
  label: string;
  icon?: string;
}

const props = defineProps<{
  chips: Chip[];
  color?: 'dark';
  /**
   * If `true`, all chips are shown and will be wrapped to the next lines if they exceed the containers max width.
   * Otherwise only chips that fit into the container width are shown and a remaining items indicator is shown.
   */
  wrap?: boolean;
}>();

const wrapper = ref<HTMLDivElement | null>(null);

const visibleChips = ref(0);
const remainingChips = computed(() => props.chips.length - visibleChips.value);
const observer = ref<IntersectionObserver>();
onBeforeUnmount(() => observer.value?.disconnect());

watchEffect(() => {
  if (observer.value && props.chips.length) {
    observer.value.disconnect();
    const chips = Array.from(wrapper.value?.querySelectorAll('scu-chip') ?? []);
    chips.forEach((chip) => observer.value?.observe(chip));
  }
});

watchEffect(() => {
  if (!wrapper.value || props.wrap) return;
  observer.value?.disconnect();

  observer.value = new IntersectionObserver(
    (res) => {
      // res contains all changed chips (not all available chips)
      // if chip is shown, intersectionRatio is 1 so remainingItems should be decremented
      // otherwise remainingItems should be increment because chips is no longer shown
      const shownChips = res.reduce((prev, curr) => (curr.intersectionRatio === 1 ? prev + 1 : prev), 0);
      const hiddenChips = res.length - shownChips;

      if (visibleChips.value <= 0) visibleChips.value = shownChips;
      else visibleChips.value += shownChips - hiddenChips;
    },
    { root: wrapper.value, threshold: 1 }
  );
});

// the indicator is placed inside the chip-list to be positioned directly after the last shown chip.
// But this also means that the indicator will be hidden if it does not fit in the max width.
// To prevent this, we watch the indicator visibility and place it outside the chip-list if it would
// not be visible inside it.
const indicatorRef = ref<HTMLElement | null>(null);
let stopIndicatorObserver: Function | undefined;
onBeforeUnmount(() => stopIndicatorObserver?.());
const isIndicatorVisible = ref(true);

watchEffect(() => {
  if (!indicatorRef.value || props.wrap) return;
  stopIndicatorObserver?.();

  const { stop } = useIntersectionObserver(
    indicatorRef,
    (res) => {
      if (!res.length) return;
      isIndicatorVisible.value = res[0].intersectionRatio === 1;
    },
    { root: wrapper, threshold: 1 }
  );

  stopIndicatorObserver = stop;
});
</script>

<template>
  <div ref="wrapper" class="chip-list">
    <div class="chip-list__wrapper" :class="{ 'chip-list__wrapper--fixed': !wrap }">
      <template v-for="(chip, index) of chips" :key="chip.label">
        <ScuChip :icon="chip.icon" :label="chip.label" :class="{ dark: color === 'dark' }" />
        <!-- place indicator directly after the last visible chip -->
        <template v-if="chips.length - 1 - remainingChips === index">
          <!-- since the span has a v-if we would receive a proxy object if directly applying ref, so we use a function instead to get the raw html element -->
          <span
            :ref="($el) => ($el ? indicatorRef = $el as HTMLElement : undefined)"
            class="chip-list__remaining"
            v-if="!wrap && remainingChips > 0"
          >
            +{{ remainingChips }}
          </span>
        </template>
      </template>
    </div>

    <!-- display indicator here if it is not visible inside the chip-list directly -->
    <span class="chip-list__remaining" v-if="!wrap && remainingChips > 0 && !isIndicatorVisible">
      +{{ remainingChips }}
    </span>
  </div>
</template>

<style lang="scss" scoped>
@use '@/styles/variables.scss' as vars;

.chip-list {
  display: flex;
  align-items: center;
  gap: vars.$spacing-s;

  &__wrapper {
    display: flex;
    align-items: center;
    overflow: hidden;
    flex-flow: row wrap;
    gap: vars.$spacing-s;

    &--fixed {
      height: 32px;
    }

    scu-chip {
      &.dark {
        --color: #ffffff;
        --background-color: #464646;
      }
    }
  }

  &__remaining {
    font-size: 13px;
    color: var(--brand-secondary-6);
  }
}
</style>

@mj-hof mj-hof changed the title Implement OnyxMoreFlyout Implement "more" feature to OnyxNavItem Apr 26, 2024
@mj-hof mj-hof changed the title Implement "more" feature to OnyxNavItem Implement "more" feature to OnyxNavBar Apr 26, 2024
@mj-hof mj-hof moved this from Backlog to Ready in onyx Apr 26, 2024
@MajaZarkova MajaZarkova self-assigned this Aug 30, 2024
@MajaZarkova MajaZarkova moved this from Ready to In Progress in onyx Aug 30, 2024
@MajaZarkova
Copy link
Contributor Author

MajaZarkova commented Sep 5, 2024

I've started implementing Lars's suggestion above using the IntersectionObserver. Here is the name of the branch with my changes -> feat/986-Implement-more-feature-to-OnyxNavBar

Current status: The behavior is not implemented correctly. The IntersectionObserver should tell you which elements are hidden, but instead it's telling you that all of them are visible, which is not the case.

@mj-hof mj-hof moved this from In Progress to Ready in onyx Sep 11, 2024
MajaZarkova pushed a commit that referenced this issue Nov 6, 2024
Relates to #986

Implement a generic `OnyxMoreList` component that can be used to render
any list of components with a "+ more" indicator.
I will integrate it in following PRs into the nav bar as well as
implementing the positioning of the more indicator which is currently
not visible at all.

## Checklist

- [x] The added / edited code has been documented with
[JSDoc](https://jsdoc.app/about-getting-started)
- [ ] If a new component is added, at least one [Playwright screenshot
test](https://github.com/SchwarzIT/onyx/actions/workflows/playwright-screenshots.yml)
is added
- [ ] A changeset is added with `npx changeset add` if your changes
should be released as npm package (because they affect the library
usage)
larsrickert added a commit that referenced this issue Dec 4, 2024
Relates to #986

Implement more indicator for OnyxMoreList.
@larsrickert larsrickert changed the title Implement "more" feature to OnyxNavBar Implement "OnyxMoreList" component Dec 4, 2024
@larsrickert larsrickert moved this from In Progress to In Approval in onyx Dec 4, 2024
@github-project-automation github-project-automation bot moved this from In Approval to Done in onyx Dec 6, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
dev Requires technical expertise
Projects
Status: Done
Development

No branches or pull requests

2 participants