Skip to content

Commit

Permalink
Merge pull request #285 from codex-team/select-component-integration
Browse files Browse the repository at this point in the history
feat: select component integration
  • Loading branch information
DeadCreator authored Feb 5, 2025
2 parents 6eb3502 + d92b7f6 commit af77679
Show file tree
Hide file tree
Showing 7 changed files with 127 additions and 57 deletions.
74 changes: 67 additions & 7 deletions codex-ui/dev/pages/components/Select.vue
Original file line number Diff line number Diff line change
Expand Up @@ -5,20 +5,73 @@
Component of the form that allows you to select one or more options from the list (currently one)
</template>
</PageHeader>
<Select
:items="options"
:default-item="defaultItem"
/>
<div class="ex">
Default
<Select
v-model="currentItem"
:align="{vertically: 'below', horizontally: 'left'}"
:is-disabled="false"
:items="options"
/>
Disabled
<Select
v-model="currentItem"
:align="{vertically: 'below', horizontally: 'left'}"
:is-disabled="true"
:items="options"
/>
</div>
<Heading :level="2">
Getting selected option
</Heading>
Selected option replaces default value and can be easily got from the same object:
<div class="ex">
<i>{{ currentItem }}</i>
</div>
<Heading
:level="2"
>
Aligning
</Heading>
You can choose vertical aligning from <code>below</code> and <code>above</code> and horizontal aligning from <code>left</code> and <code>right</code>
<div class="ex">
<Select
v-model="currentItem"
:align="{vertically: 'below', horizontally: 'left'}"
:is-disabled="false"
:items="options"
/>
<Select
v-model="currentItem"
:align="{vertically: 'below', horizontally: 'right'}"
:is-disabled="false"
:items="options"
/>
<Select
v-model="currentItem"
:align="{vertically: 'above', horizontally: 'left'}"
:is-disabled="false"
:items="options"
/>
<Select
v-model="currentItem"
:align="{vertically: 'above', horizontally: 'right'}"
:is-disabled="false"
:items="options"
/>
</div>
</template>

<script setup lang="ts">
import PageHeader from '../../components/PageHeader.vue';
import { ContextMenuItem, DefaultItem, Select } from '../../../src';
import { ContextMenuItem, Heading, Select } from '../../../src';
import { ref } from 'vue';
const defaultItem: DefaultItem = {
const currentItem = ref({
title: 'Choose an option',
onActivate: () => {},
};
});
const options: ContextMenuItem[] = [
{
type: 'default',
Expand Down Expand Up @@ -50,4 +103,11 @@ const options: ContextMenuItem[] = [
</script>

<style scoped>
.ex {
display: grid;
gap: var(--spacing-xl);
margin: var(--spacing-xl) 0 var(--spacing-xxl);
grid-template-columns: repeat(2, max-content);
align-items: center;
}
</style>
2 changes: 1 addition & 1 deletion codex-ui/src/vue/components/popover/Popover.vue
Original file line number Diff line number Diff line change
Expand Up @@ -51,10 +51,10 @@ onClickOutside(popoverEl, hide, {
border-radius: var(--radius-field);
border: 1px solid var(--base--border);
padding: var(--h-padding);
box-sizing: border-box;
left: v-bind('position.left');
top: v-bind('position.top');
transform: v-bind('position.transform');
width: v-bind('width');
box-sizing: border-box;
}
</style>
20 changes: 10 additions & 10 deletions codex-ui/src/vue/components/popover/usePopover.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,8 @@ export const usePopover = createSharedComposable(() => {
* Popover position info used in the Popover component
*/
const position = reactive({
left: 0,
top: 0,
left: '0px',
top: '0px',
transform: 'translate(0, 0)',
});

Expand Down Expand Up @@ -68,29 +68,29 @@ export const usePopover = createSharedComposable(() => {

const rect = targetEl.getBoundingClientRect();

let top = 0;
let left = 0;
let top = '0px';
let left = '0px';
let transformX = '0';
let transformY = '0';

switch (align.vertically) {
case 'above':
top = rect.top - MARGIN + window.scrollY;
top = `${rect.top - MARGIN + window.scrollY}px`;
transformY = '-100%';
break;
case 'below':
top = rect.bottom + MARGIN + window.scrollY;
top = `${rect.bottom + MARGIN + window.scrollY}px`;
transformY = '0';
break;
}

switch (align.horizontally) {
case 'left':
left = rect.left;
left = `${rect.left}px`;
transformX = '0';
break;
case 'right':
left = rect.right;
left = `${rect.right}px`;
transformX = '-100';
break;
}
Expand Down Expand Up @@ -145,8 +145,8 @@ export const usePopover = createSharedComposable(() => {
function resetPopover(): void {
targetElement.value = null;
content.value = null;
position.left = 0;
position.top = 0;
position.left = '0px';
position.top = '0px';
position.transform = 'translate(0, 0)';

isOpen.value = false;
Expand Down
22 changes: 12 additions & 10 deletions codex-ui/src/vue/components/select/Select.vue
Original file line number Diff line number Diff line change
@@ -1,30 +1,33 @@
<template>
<Button
:icon="activeItem.icon"
:icon="currentItem.icon"
:disabled="isDisabled"
trailing-icon="BracketsVertical"
secondary
@click="togglePopover($event.currentTarget, {vertically: 'below', horizontally: 'left'})"
@click="togglePopover($event.currentTarget)"
>
{{ activeItem.title }}
{{ currentItem.title }}
</Button>
</template>
<script setup lang="ts">
import type { ContextMenuItem as SelectItem, DefaultItem } from '../context-menu/ContextMenu.types';
import { ContextMenu } from '../context-menu';
import { onMounted, ref } from 'vue';
import { onMounted } from 'vue';
import { usePopover, PopoverShowParams } from '../popover';
import { Button } from '../button';
const props = defineProps<{
align: PopoverShowParams['align'];
isDisabled: boolean;
items: SelectItem[];
defaultItem: DefaultItem;
}>();
const align = props.align;
const items = props.items;
const { showPopover, hide, isOpen } = usePopover();
const togglePopover = (el: HTMLElement, align: PopoverShowParams['align']) => {
if (!isOpen.value) {
const togglePopover = (el: HTMLElement) => {
if (!isOpen.value && !props.isDisabled) {
showPopover({
targetEl: el,
with: {
Expand All @@ -41,12 +44,11 @@ const togglePopover = (el: HTMLElement, align: PopoverShowParams['align']) => {
};
/* Default item value for select on page load */
const defaultValue: SelectItem = props.defaultItem;
const activeItem = ref(defaultValue);
const currentItem = defineModel<DefaultItem>({ required: true });
/* Main function to update selected item */
const updateActiveItem = (item: DefaultItem) => {
activeItem.value = item;
currentItem.value = item;
hide();
};
Expand Down
5 changes: 5 additions & 0 deletions src/domain/entities/Note.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,4 +51,9 @@ export interface Note {
* @todo Resolve the optionality issue
*/
updatedAt?: string;

/**
* Id of user, who created the note
*/
creatorId?: number;
}
60 changes: 31 additions & 29 deletions src/presentation/components/team/RoleSelect.vue
Original file line number Diff line number Diff line change
@@ -1,27 +1,22 @@
<template>
<div v-if="teamMember.user.id != user?.id">
<select
<div>
<Select
v-model="selectedRole"
@change="updateMemberRole"
>
<option
v-for="(role, index) in roleOptions"
:key="index"
:value="role"
>
{{ t(`noteSettings.team.roles.${role}`) }}
</option>
</select>
:align="{ vertically: 'below', horizontally: 'right' }"
:is-disabled="teamMember.user.id == user?.id || (note !== null && (note as Note).creatorId === teamMember.id)"
:items="roleItems"
/>
</div>
</template>

<script setup lang="ts">
import { MemberRole, TeamMember } from '@/domain/entities/Team.ts';
import { NoteId } from '@/domain/entities/Note.ts';
import { computed, ref } from 'vue';
import { Note, NoteId } from '@/domain/entities/Note.ts';
import { computed, ref, watch } from 'vue';
import useNoteSettings from '@/application/services/useNoteSettings.ts';
import { useAppState } from '@/application/services/useAppState';
import { useI18n } from 'vue-i18n';
import { ContextMenuItem, DefaultItem, Select } from 'codex-ui/vue';
import useNote from '@/application/services/useNote.ts';
/**
* TeamMember props
Expand All @@ -37,31 +32,38 @@ const props = defineProps<{
noteId: NoteId;
}>();
const selectedRole = ref(MemberRole[props.teamMember.role]);
const selectedRole = ref<DefaultItem>({
title: MemberRole[props.teamMember.role],
onActivate: () => {},
});
const roleOptions = computed(() => Object.values(MemberRole).filter(value => typeof value === 'string'));
const roleItems: ContextMenuItem[] = [];
const { changeRole } = useNoteSettings();
roleOptions.value.forEach((role) => {
roleItems.push({
title: role.toString(),
onActivate: () => {},
});
});
const { changeRole } = useNoteSettings();
const { note } = useNote({ id: props.noteId });
const { user } = useAppState();
const { t } = useI18n();
/* Watch role's update */
watch(selectedRole, (newRole) => {
updateMemberRole(newRole.title);
});
/**
* Updates the user role if it has been changed
*
* @param updatedRole - new role needed to set
*/
async function updateMemberRole() {
changeRole(props.noteId, props.teamMember.user.id, MemberRole[selectedRole.value as keyof typeof MemberRole]);
async function updateMemberRole(updatedRole: string | any) {
changeRole(props.noteId, props.teamMember.user.id, MemberRole[updatedRole as keyof typeof MemberRole]);
}
</script>

<style scoped>
.member {
margin-top: var(--spacing-l);
}
.member-name {
display: flex;
align-items: center;
gap: var(--spacing-very-x);
}
</style>
1 change: 1 addition & 0 deletions src/presentation/components/team/Team.vue
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
:key="member.id"
:title="member.user.name || member.user.email"
:has-delimiter="memberIndex !== team.length - 1"
data-dimensions="medium"
>
<template #right>
<RoleSelect
Expand Down

0 comments on commit af77679

Please sign in to comment.