Merge branch 'develop' into feat/contacts-redesign

This commit is contained in:
Sivin Varghese
2024-11-22 08:09:28 +05:30
committed by GitHub
7 changed files with 271 additions and 34 deletions
@@ -0,0 +1,49 @@
<script setup>
import { ref } from 'vue';
import { useI18n } from 'vue-i18n';
import DropdownMenu from 'dashboard/components-next/dropdown-menu/DropdownMenu.vue';
defineProps({
labelMenuItems: {
type: Array,
default: () => [],
},
});
const emit = defineEmits(['updateLabel']);
const { t } = useI18n();
const showDropdown = ref(false);
</script>
<template>
<div class="relative">
<button
class="flex items-center gap-1 px-2 py-1 rounded-md outline-dashed h-[26px] outline-1 outline-n-slate-6 hover:bg-n-alpha-2"
:class="{ 'bg-n-alpha-2': showDropdown }"
@click="showDropdown = !showDropdown"
>
<span class="i-lucide-plus" />
<span class="text-sm text-n-slate-11">
{{ t('LABEL.TAG_BUTTON') }}
</span>
</button>
<DropdownMenu
v-if="showDropdown"
v-on-clickaway="() => (showDropdown = false)"
:menu-items="labelMenuItems"
show-search
class="z-[100] w-48 mt-2 overflow-y-auto ltr:left-0 rtl:right-0 top-full max-h-52"
@action="emit('updateLabel', $event)"
>
<template #thumbnail="{ item }">
<div
class="rounded-sm size-2"
:style="{ backgroundColor: item.thumbnail.color }"
/>
</template>
</DropdownMenu>
</div>
</template>
@@ -0,0 +1,22 @@
<script setup>
defineProps({
label: {
type: Object,
default: null,
},
});
</script>
<template>
<div
class="bg-n-alpha-2 rounded-md flex items-center h-7 w-fit py-1 ltr:pl-1 rtl:pr-1 ltr:pr-1.5 rtl:pl-1.5"
>
<div
class="w-2 h-2 m-1 rounded-sm"
:style="{ backgroundColor: label.color }"
/>
<span class="text-sm text-n-slate-12">
{{ label.title }}
</span>
</div>
</template>
@@ -0,0 +1,27 @@
<script setup>
import AddLabel from '../AddLabel.vue';
import { labelMenuItems } from './fixtures';
function onUpdateLabel(label) {
console.log('Label updated:', label);
}
</script>
<template>
<Story title="Components/Label/Add Label">
<Variant title="Default (button with label menu items with active state)">
<div class="h-[300px] p-4">
<AddLabel
:label-menu-items="labelMenuItems"
@update-label="onUpdateLabel"
/>
</div>
</Variant>
<Variant title="Empty List (button with empty label menu)">
<div class="h-[300px] p-4">
<AddLabel :label-menu-items="[]" @update-label="onUpdateLabel" />
</div>
</Variant>
</Story>
</template>
@@ -0,0 +1,21 @@
<script setup>
import Label from '../LabelItem.vue';
import { label } from './fixtures';
</script>
<template>
<Story title="Components/Label/Label item">
<Variant title="Default">
<Label :label="label" />
</Variant>
<Variant title="Custom Label">
<Label
:label="{
title: 'Custom Label',
color: '#FF5733',
}"
/>
</Variant>
</Story>
</template>
@@ -0,0 +1,62 @@
export const label = {
id: 1,
title: 'delivery',
color: '#A2FDD5',
};
export const labelMenuItems = [
{
label: 'delivery',
value: 3,
thumbnail: {
color: '#A2FDD5',
},
isSelected: true,
action: 'addLabel',
},
{
label: 'lead',
value: 6,
thumbnail: {
color: '#F161C8',
},
isSelected: false,
action: 'addLabel',
},
{
label: 'ops-handover',
value: 4,
thumbnail: {
color: '#A53326',
},
isSelected: false,
action: 'addLabel',
},
{
label: 'billing',
value: 1,
thumbnail: {
color: '#28AD21',
},
isSelected: false,
action: 'addLabel',
},
{
label: 'premium-customer',
value: 5,
thumbnail: {
color: '#6FD4EF',
},
isSelected: false,
action: 'addLabel',
},
{
label: 'software',
value: 2,
thumbnail: {
color: '#8F6EF2',
},
isSelected: false,
action: 'addLabel',
},
];
@@ -1,46 +1,85 @@
<script setup>
import { computed, useSlots } from 'vue';
import { computed, useSlots, useAttrs } from 'vue';
import Spinner from 'dashboard/components-next/spinner/Spinner.vue';
import Icon from 'dashboard/components-next/icon/Icon.vue';
import {
VARIANT_OPTIONS,
COLOR_OPTIONS,
SIZE_OPTIONS,
EXCLUDED_ATTRS,
} from './constants.js';
const props = defineProps({
label: {
type: [String, Number],
default: '',
},
label: { type: [String, Number], default: '' },
variant: {
type: String,
default: 'solid',
validator: value =>
['solid', 'outline', 'faded', 'link', 'ghost'].includes(value),
default: null,
validator: value => VARIANT_OPTIONS.includes(value) || value === null,
},
color: {
type: String,
default: 'blue',
validator: value =>
['blue', 'ruby', 'amber', 'slate', 'teal'].includes(value),
default: null,
validator: value => COLOR_OPTIONS.includes(value) || value === null,
},
size: {
type: String,
default: 'md',
validator: value => ['xs', 'sm', 'md', 'lg'].includes(value),
},
icon: {
type: String,
default: '',
},
trailingIcon: {
type: Boolean,
default: false,
},
isLoading: {
type: Boolean,
default: false,
default: null,
validator: value => SIZE_OPTIONS.includes(value) || value === null,
},
icon: { type: [String, Object, Function], default: '' },
trailingIcon: { type: Boolean, default: false },
isLoading: { type: Boolean, default: false },
});
const slots = useSlots();
const attrs = useAttrs();
defineOptions({
inheritAttrs: false,
});
const filteredAttrs = computed(() => {
const standardAttrs = {};
Object.entries(attrs)
.filter(([key]) => !EXCLUDED_ATTRS.includes(key))
.forEach(([key, value]) => {
standardAttrs[key] = value;
});
return standardAttrs;
});
const computedVariant = computed(() => {
if (props.variant) return props.variant;
// The useAttrs method returns attributes values an empty string (not boolean value as in props).
if (attrs.solid || attrs.solid === '') return 'solid';
if (attrs.outline || attrs.outline === '') return 'outline';
if (attrs.faded || attrs.faded === '') return 'faded';
if (attrs.link || attrs.link === '') return 'link';
if (attrs.ghost || attrs.ghost === '') return 'ghost';
return 'solid'; // Default variant
});
const computedColor = computed(() => {
if (props.color) return props.color;
if (attrs.blue || attrs.blue === '') return 'blue';
if (attrs.ruby || attrs.ruby === '') return 'ruby';
if (attrs.amber || attrs.amber === '') return 'amber';
if (attrs.slate || attrs.slate === '') return 'slate';
if (attrs.teal || attrs.teal === '') return 'teal';
return 'blue'; // Default color
});
const computedSize = computed(() => {
if (props.size) return props.size;
if (attrs.xs || attrs.xs === '') return 'xs';
if (attrs.sm || attrs.sm === '') return 'sm';
if (attrs.md || attrs.md === '') return 'md';
if (attrs.lg || attrs.lg === '') return 'lg';
return 'md';
});
const STYLE_CONFIG = {
colors: {
@@ -113,23 +152,24 @@ const STYLE_CONFIG = {
const variantClasses = computed(() => {
const variantMap = {
ghost: 'text-n-slate-12 hover:bg-n-alpha-2 outline-transparent',
link: `${STYLE_CONFIG.colors[props.color].link} p-0 font-medium underline-offset-4`,
outline: STYLE_CONFIG.colors[props.color].outline,
faded: STYLE_CONFIG.colors[props.color].faded,
solid: STYLE_CONFIG.colors[props.color].solid,
link: `${STYLE_CONFIG.colors[computedColor.value].link} p-0 font-medium underline-offset-4`,
outline: STYLE_CONFIG.colors[computedColor.value].outline,
faded: STYLE_CONFIG.colors[computedColor.value].faded,
solid: STYLE_CONFIG.colors[computedColor.value].solid,
};
return variantMap[props.variant];
return variantMap[computedVariant.value];
});
const isIconOnly = computed(() => !props.label && !slots.default);
const isLink = computed(() => props.variant === 'link');
const isLink = computed(() => computedVariant.value === 'link');
const buttonClasses = computed(() => {
const sizeConfig = isIconOnly.value ? 'iconOnly' : 'regular';
const classes = [
variantClasses.value,
props.variant !== 'link' && STYLE_CONFIG.sizes[sizeConfig][props.size],
computedVariant.value !== 'link' &&
STYLE_CONFIG.sizes[sizeConfig][computedSize.value],
].filter(Boolean);
return classes.join(' ');
@@ -138,7 +178,7 @@ const buttonClasses = computed(() => {
const linkButtonClasses = computed(() => {
const classes = [
variantClasses.value,
STYLE_CONFIG.sizes.link[props.size],
STYLE_CONFIG.sizes.link[computedSize.value],
].filter(Boolean);
return classes.join(' ');
@@ -147,10 +187,11 @@ const linkButtonClasses = computed(() => {
<template>
<button
v-bind="filteredAttrs"
:class="{
[STYLE_CONFIG.base]: true,
[isLink ? linkButtonClasses : buttonClasses]: true,
[STYLE_CONFIG.fontSize[size]]: true,
[STYLE_CONFIG.fontSize[computedSize]]: true,
'flex-row-reverse': trailingIcon && !isIconOnly,
}"
>
@@ -0,0 +1,15 @@
export const VARIANT_OPTIONS = ['solid', 'outline', 'faded', 'link', 'ghost'];
export const COLOR_OPTIONS = ['blue', 'ruby', 'amber', 'slate', 'teal'];
export const SIZE_OPTIONS = ['xs', 'sm', 'md', 'lg'];
export const EXCLUDED_ATTRS = [
'variant',
'color',
'size',
'icon',
'trailingIcon',
'isLoading',
...VARIANT_OPTIONS,
...COLOR_OPTIONS,
...SIZE_OPTIONS,
];