Skip to content
Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -31,14 +31,17 @@
</div>

<div v-if="!masked" class="flex flex-col gap-4">
<lf-contributor-details-work-history-item
v-for="org of (orgs || []).slice(0, showMore ? (orgs || []).length : 3)"
:key="org.id"
:contributor="props.contributor"
:organization="org"
@edit="isEditModalOpen = true; editOrganization = org"
/>
<div v-if="orgs.length === 0" class="pt-2 flex flex-col items-center">
<lf-timeline v-slot="{ group }" :groups="shownGroups" @on-group-hover="onGroupHover">
<lf-timeline-item v-for="item in group.items" :key="item.id" :data="item">
<lf-contributor-details-work-history-item
:contributor="props.contributor"
:organization="item"
:is-group-hover="hoveredGroup?.id === group.id"
@edit="isEditModalOpen = true; editOrganization = item"
/>
</lf-timeline-item>
</lf-timeline>
<div v-if="orgGrouped.length === 0" class="pt-2 flex flex-col items-center">
<lf-icon-old name="survey-line" :size="40" class="text-gray-300" />
<p class="text-center pt-3 text-medium text-gray-400">
No work experiences
Expand All @@ -55,7 +58,7 @@
</div>

<lf-button
v-if="!masked && orgs.length > 3"
v-if="!masked && orgGrouped.length > minimumShownGroups"
type="primary-link"
size="medium"
class="mt-6"
Expand Down Expand Up @@ -88,21 +91,52 @@ import LfContributorDetailsWorkHistoryItem
from '@/modules/contributor/components/details/work-history/contributor-details-work-history-item.vue';
import useContributorHelpers from '@/modules/contributor/helpers/contributor.helpers';
import LfIcon from '@/ui-kit/icon/Icon.vue';
import { TimelineGroup } from '@/ui-kit/timeline/types/TimelineTypes';
import { groupBy } from 'lodash';
import { storeToRefs } from 'pinia';
import { useLfSegmentsStore } from '@/modules/lf/segments/store';
import LfTimeline from '@/ui-kit/timeline/Timeline.vue';
import LfTimelineItem from '@/ui-kit/timeline/TimelineItem.vue';

const props = defineProps<{
contributor: Contributor,
}>();

const { hasPermission } = usePermissions();
const { isMasked } = useContributorHelpers();
const { selectedProjectGroup } = storeToRefs(useLfSegmentsStore());

const showMore = ref<boolean>(false);
const isEditModalOpen = ref<boolean>(false);
const editOrganization = ref<Organization | null>(null);
const hoveredGroup = ref<TimelineGroup | null>(null);

const orgs = computed(() => props.contributor.organizations);
const orgGrouped = computed(() => {
const grouped = groupBy(props.contributor.organizations, 'id');
return Object.keys(grouped).map((id, index): TimelineGroup => ({
id: index,
label: grouped[id][0].displayName,
labelLink: {
name: 'organizationView',
params: {
id,
},
query: {
projectGroup: selectedProjectGroup.value?.id,
},
},
icon: grouped[id][0].logo,
items: grouped[id],
}));
});
const minimumShownGroups = 3;
const shownGroups = computed(() => orgGrouped.value.slice(0, showMore.value ? orgGrouped.value.length : minimumShownGroups));

const masked = computed(() => isMasked(props.contributor));

const onGroupHover = (index: TimelineGroup | null) => {
hoveredGroup.value = index;
};
</script>

<script lang="ts">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,37 +4,8 @@
@mouseleave="hovered = false"
>
<div class="flex">
<lf-avatar
:name="props.organization.displayName"
:src="props.organization.logo"
:size="24"
class="!rounded-md border border-gray-200 min-w-6"
img-class="!object-contain"
>
<template #placeholder>
<div class="w-full h-full bg-gray-50 flex items-center justify-center">
<lf-icon-old name="community-line" :size="16" class="text-gray-400" />
</div>
</template>
</lf-avatar>

<div class="pl-3 flex flex-auto flex-col overflow-hidden">
<router-link
:to="{
name: 'organizationView',
params: {
id: props.organization.id,
},
query: {
projectGroup: selectedProjectGroup?.id,
},
}"
class="font-semibold text-medium leading-6 mb-1 truncate text-black hover:text-primary-500 transition block w-full overflow-hidden"
>
{{ props.organization.displayName }}
</router-link>

<div v-if="props.organization?.memberOrganizations?.title" class="text-small text-gray-500 mb-1.5 flex items-center gap-1.5">
<div class="flex flex-auto flex-col overflow-hidden">
<div v-if="props.organization?.memberOrganizations?.title" class="text-small text-gray-900 mb-1.5 flex items-center gap-1.5">
<lf-svg name="id-card" class="h-4 w-4 text-gray-400" />
<p class="truncate" style="max-width: 30ch">
{{ props.organization?.memberOrganizations?.title }}
Expand All @@ -46,7 +17,7 @@
</p>
</div>

<lf-dropdown v-if="hovered" placement="bottom-end" width="14.5rem">
<lf-dropdown v-if="hovered || isGroupHover" placement="bottom-end" width="14.5rem">
<template #trigger>
<lf-button type="secondary-ghost" size="small" :icon-only="true">
<lf-icon name="ellipsis" />
Expand Down Expand Up @@ -80,11 +51,8 @@
import LfIconOld from '@/ui-kit/icon/IconOld.vue';
import { Contributor } from '@/modules/contributor/types/Contributor';
import LfSvg from '@/shared/svg/svg.vue';
import LfAvatar from '@/ui-kit/avatar/Avatar.vue';
import { Organization } from '@/modules/organization/types/Organization';
import moment from 'moment';
import { storeToRefs } from 'pinia';
import { useLfSegmentsStore } from '@/modules/lf/segments/store';
import LfButton from '@/ui-kit/button/Button.vue';
import LfDropdown from '@/ui-kit/dropdown/Dropdown.vue';
import LfDropdownItem from '@/ui-kit/dropdown/DropdownItem.vue';
Expand All @@ -103,12 +71,12 @@ import LfIcon from '@/ui-kit/icon/Icon.vue';

const props = defineProps<{
organization: Organization,
contributor: Contributor
contributor: Contributor,
isGroupHover: boolean,
}>();

const emit = defineEmits<{(e:'edit'): void}>();

const { selectedProjectGroup } = storeToRefs(useLfSegmentsStore());
const { deleteContributorOrganization } = useContributorStore();
const { trackEvent } = useProductTracking();

Expand Down
53 changes: 53 additions & 0 deletions frontend/src/ui-kit/timeline/Timeline.stories.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import LfSvg from '@/shared/svg/svg.vue';
import LfIconOld from '@/ui-kit/icon/IconOld.vue';
import LfTimeline from './Timeline.vue';
import LfTimelineItem from './TimelineItem.vue';
import { TimelineGroup } from './types/TimelineTypes';

export default {
title: 'LinuxFoundation/Timeline',
component: LfTimeline,
tags: ['autodocs'],
argTypes: {
groups: {
description: 'Timeline groups',
control: 'object',
},
},
};

export const Regular = {
args: {
groups: [
{
label: 'Group 1', labelLink: { name: 'organizationView', params: { id: '1' } }, icon: 'https://avatars.githubusercontent.com/u/38015056?v=4', items: [{ id: 1, label: 'Item 1', date: 'Aug 2024 - Dec 2024' }, { id: 2, label: 'Item 2', date: 'Aug 2024' }, { id: 3, label: 'Item 3', date: 'Aug 2024' }, { id: 4, label: 'Item 4', date: 'Aug 2024' }],
},
{
label: 'Group 2', labelLink: { name: 'organizationView', params: { id: '2' } }, icon: 'https://avatars.githubusercontent.com/u/38015056?v=4', items: [{ id: 3, label: 'Item 3', date: 'Aug 2024' }, { id: 4, label: 'Item 4', date: 'Aug 2024' }],
},
],
},
render: (args: { groups: TimelineGroup[] }) => ({
components: {
LfTimeline, LfTimelineItem, LfSvg, LfIconOld,
},
setup() {
return { args };
},
template: `<lf-timeline :groups="args.groups" v-slot="{ group }">
<lf-timeline-item :data="item" v-for="item in group.items" :key="item.id">
<!-- SAMPLE CONTENT -->
<div class="text-small text-gray-900 mb-1.5 flex items-center gap-1.5">
<lf-svg name="id-card" class="h-4 w-4 text-gray-400" />
<p class="truncate text-gray-900" style="max-width: 30ch">
{{ item.label }}
</p>
</div>
<p class="text-small text-gray-500 mb-1.5 flex items-center">
<lf-icon-old name="calendar-line" :size="16" class="mr-1.5 text-gray-400" />
{{ item.date }}
</p>
</lf-timeline-item>
</lf-timeline>`,
}),
};
60 changes: 60 additions & 0 deletions frontend/src/ui-kit/timeline/Timeline.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
<template>
<div
v-for="group in props.groups"
:key="group.label"
class="c-timeline"
@mouseover="emit('onGroupHover', group)"
@mouseleave="emit('onGroupHover', null)"
>
<div class="c-timeline__group-icon">
<lf-avatar
:name="group.label"
:src="group.icon"
:size="24"
class="!rounded-md border border-gray-200 min-w-6"
img-class="!object-contain"
/>
</div>
<div class="grow">
<div class="c-timeline__group-label">
<router-link
v-if="group.labelLink"
:to="group.labelLink"
class="cursor-pointer text-gray-900 hover:text-primary-500"
>
{{ group.label }}
</router-link>
<span v-else>
{{ group.label }}
</span>
</div>

<div class="c-timeline__items">
<slot name="default" :group="group" :hovered="hovered" />
</div>
</div>
</div>
</template>

<script lang="ts" setup>
import LfAvatar from '@/ui-kit/avatar/Avatar.vue';
import { ref } from 'vue';
import { TimelineGroup } from './types/TimelineTypes';

const emit = defineEmits(['onGroupHover']);

const props = defineProps<{
groups: TimelineGroup[];
}>();

const hovered = ref(false);

</script>

<script lang="ts">
export default {
name: 'LfTimeline',
};
</script>

<style scoped lang="scss" src="./timeline.scss" />
19 changes: 19 additions & 0 deletions frontend/src/ui-kit/timeline/TimelineItem.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<template>
<div class="c-timeline__item">
<div class="c-timeline__item-dot" />
<div class="c-timeline__item-content">
<slot :data="props.data" />
</div>
</div>
</template>

<script lang="ts" setup>
const props = defineProps<{
data: any;
}>();
</script>
<script lang="ts">
export default {
name: 'LfTimelineItem',
};
</script>
40 changes: 40 additions & 0 deletions frontend/src/ui-kit/timeline/timeline.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
.c-timeline {
@apply flex items-start gap-3;
&__group-label {
@apply flex items-center font-semibold text-medium leading-6 mb-1 truncate text-black hover:text-primary-500 transition block w-full overflow-hidden;
}
// &__border {
// @apply w-px h-full bg-gray-200;
// }

::v-deep .c-timeline__item {
@apply relative mb-4;
.c-timeline__item-dot {
@apply w-3 h-3 z-[2] bg-gray-200 rounded-full absolute top-[2px] -left-[30px] border-3 border-white;
}
.c-timeline__item-content {
@apply relative;
&::before {
content: '';
@apply w-px bg-gray-200 absolute top-[2px] left-[-25px];
height: calc(100% + 25px);
}
}

&:first-child {
.c-timeline__item-dot {
@apply invisible;
}
}

&:last-child {
.c-timeline__item-content {
&::before {
@apply h-[5px];
}
}
}
}
}


9 changes: 9 additions & 0 deletions frontend/src/ui-kit/timeline/types/TimelineTypes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import type { RouteLocationRaw } from 'vue-router';

export interface TimelineGroup {
id: number;
label: string;
labelLink?: RouteLocationRaw;
icon?: string;
items: any[];
}
Loading