Skip to content
Snippets Groups Projects
Commit 8641adb7 authored by Дмитрий Малюгин's avatar Дмитрий Малюгин :clock4:
Browse files

feat: component 'Select'

parent 599e574a
No related branches found
No related tags found
1 merge request!3Table (partially), Checkbox, Tag, Select and Knob
File added
nodeLinker: node-modules
......@@ -95,6 +95,7 @@ import PlusCircleIcon from '@stories/icons/Mono/PlusCircleIcon.vue';
import PlusIcon from '@stories/icons/Mono/PlusIcon.vue';
import PointerIcon from '@stories/icons/Mono/PointerIcon.vue';
import SaveIcon from '@stories/icons/Mono/SaveIcon.vue';
import SearchIcon from '@stories/icons/Mono/SearchIcon.vue';
import SettingsIcon from '@stories/icons/Mono/SettingsIcon.vue';
import SortHorizontalIcon from '@stories/icons/Mono/SortHorizontalIcon.vue';
import SortDownIcon from '@stories/icons/Mono/SortDownIcon.vue';
......@@ -209,6 +210,7 @@ const gentleIcons = {
PlusIcon,
PointerIcon,
SaveIcon,
SearchIcon,
SettingsIcon,
SortDownIcon,
SortHorizontalIcon,
......
......@@ -108,6 +108,7 @@ import SortDownIcon from '@stories/icons/Mono/SortDownIcon.vue';
import SortUpIcon from '@stories/icons/Mono/SortUpIcon.vue';
import SortVerticalIcon from '@stories/icons/Mono/SortVerticalIcon.vue';
import ArrowShortDownIcon from '@stories/icons/Mono/ArrowShortDownIcon.vue';
import SearchIcon from '@stories/icons/Mono/SearchIcon.vue';
export const iconsSet: Record<string, Component> = {
Age18: Age18Icon,
......@@ -209,6 +210,7 @@ export const iconsSet: Record<string, Component> = {
Plus: PlusIcon,
Pointer: PointerIcon,
Save: SaveIcon,
SearchIcon,
Settings: SettingsIcon,
SortDownIcon,
SortHorizontalIcon,
......
......@@ -71,7 +71,9 @@ export interface ISelectOption {
export interface ISelectGroup {
name: string;
titleColor?: TThemeColor;
nameColor?: TThemeColor;
background?: TThemeColor;
items?: ISelectOption[];
iconLeft?: TIcons;
iconRight?: TIcons;
iconLeftColor?: TThemeColor;
......
......@@ -139,6 +139,7 @@ export interface ISelectProps {
darknessTheme?: TDarkness;
darknessBackground?: TDarkness;
darknessOpenIcon?: TDarkness;
filtered?: boolean;
disabled?: boolean;
}
......
......@@ -16,6 +16,7 @@ const meta: Meta = {
},
argTypes: {
width: { control: 'text' },
filtered: { control: 'boolean' },
disabled: { control: 'boolean' },
placeholder: { control: 'text' },
name: { control: 'text' },
......@@ -135,19 +136,28 @@ export const Full: Story = {
iconLeft: 'At',
color: 'purple',
darknessColor: '800',
group: 'Group',
},
{
value: 'Second',
iconRightColor: 'red',
iconRight: 'Age18',
group: 'Group',
},
{
iconLeft: 'Calendar',
value: 'Third',
iconRight: 'CheckMark',
group: 'Group 2',
},
{
value: 'Sssss',
},
],
groups: [
{ name: 'Group', background: 'white', iconLeft: 'Archive' },
{ name: 'Group 2', background: 'red', iconLeft: 'Badge' },
],
placeholder: 'Select a city',
size: 'normal',
width: '250px',
......
......@@ -5,6 +5,8 @@ import { convertThemeToColor } from '@helpers/common';
import { iconsSet } from '@/common/constants/icons';
import type { TThemeColor } from '@interfaces/common';
import SelectItem from '@stories/components/Select/SelectItem.vue';
import SearchIcon from '@stories/icons/Mono/SearchIcon.vue';
import { calcFontSize, calcPadding, getOptionsGroups } from '@stories/components/Select/helpers';
const props = withDefaults(defineProps<ISelectProps>(), {
size: 'normal',
......@@ -19,8 +21,20 @@ const props = withDefaults(defineProps<ISelectProps>(), {
});
const selected = defineModel('value');
const isOpen = ref<boolean>(false);
const filter = ref<string>('');
const optionsGroups = computed(() => getOptionsGroups(props.options, props.groups, filter.value));
const optionsNoGroup = computed(() =>
props.options.filter(
(option) =>
!option.group &&
(filter.value ? (option.label ?? option.value).toLowerCase().startsWith(filter.value.toLowerCase()) : true),
),
);
const selectedOption = computed(() => props.options.find((option) => option.value === selected.value));
const fontSize = computed(() => calcFontSize(props.size));
const fontSizeNumber = computed(() => fontSize.value.slice(0, -2));
const padding = computed(() => calcPadding(props.size));
const textColor = computed(() => (props.disabled ? '#62708c' : convertThemeToColor(props.theme, props.darknessTheme)));
const backgroundColor = computed(() =>
convertThemeToColor(
......@@ -28,20 +42,6 @@ const backgroundColor = computed(() =>
(!props.background && props.theme === 'black') || props.background === 'white' ? '500' : props.darknessBackground,
),
);
const fontSize = computed(() => {
const size = props.size;
if (size === 'normal') return '16px';
if (size === 'large') return '20px';
if (size === 'huge') return '24px';
return '12px';
});
const padding = computed(() => {
const size = props.size;
if (size === 'normal') return '6px';
if (size === 'large') return '10px';
if (size === 'huge') return '14px';
return '4px';
});
const pickOption = (value: string) => {
selected.value = value;
......@@ -74,7 +74,7 @@ const calcOptionColor = (color: TThemeColor | undefined, darknessColor: string |
class="selected"
:style="`color: ${selected ? calcOptionColor(selectedOption?.color, selectedOption?.darknessColor, textColor) : placeholderColor ? convertThemeToColor(placeholderColor, '700') : '#62708c'}; font-weight: 600`"
:option="selectedOption"
:fontSize="fontSize"
:fontSizeNumber="fontSizeNumber"
:textColor="textColor"
>
<slot :name="`icon-left-${selectedOption?.value}`"></slot>
......@@ -83,7 +83,7 @@ const calcOptionColor = (color: TThemeColor | undefined, darknessColor: string |
</SelectItem>
<component
:is="iconsSet[openIcon]"
:size="fontSize.slice(0, -2)"
:size="fontSizeNumber"
:color="openIconColor ? convertThemeToColor(openIconColor, darknessOpenIcon) : '#62708c'"
:style="`width: ${fontSize}`"
/>
......@@ -97,12 +97,56 @@ const calcOptionColor = (color: TThemeColor | undefined, darknessColor: string |
]"
>
<div style="overflow: hidden">
<div class="flex filter" v-if="filtered">
<input v-model="filter" type="text" /><SearchIcon :size="fontSizeNumber" color="#62708c" />
</div>
<div v-for="group of optionsGroups" :key="group.name" class="group">
<h3
class="flexNoHover groupHeader"
:style="`color: ${calcOptionColor(group.nameColor, darknessTheme, textColor)};
background-color: ${calcOptionColor(group.background, group.background === 'white' ? '500' : '200', backgroundColor)}; font-size: calc(${fontSize} * 0.8); padding: calc(${padding} * 0.8)`"
>
<component
v-if="group?.iconLeft"
:is="iconsSet[group?.iconLeft]"
:size="fontSizeNumber"
:color="calcOptionColor(group?.iconLeftColor, darknessTheme, textColor)"
/>{{ group.name }}
<component
v-if="group?.iconRight"
:is="iconsSet[group?.iconRight]"
:size="fontSizeNumber"
:color="calcOptionColor(group?.iconRightColor, darknessTheme, textColor)"
/>
</h3>
<SelectItem
@click.prevent="pickOption(option.value)"
v-for="option of group.items"
:key="option.value"
:class="[
'flex',
{
firstOption: options[0].value === option.value,
lastOption: options[options.length - 1].value === option.value,
},
]"
:style="`color: ${calcOptionColor(option.color, option.darknessColor, textColor)};
background-color: ${calcOptionColor(option.background, option.darknessBackground, backgroundColor)}`"
:option="option"
:fontSizeNumber="fontSizeNumber"
:textColor="textColor"
>
<slot :name="`icon-left-${option.value}`"></slot>
<span :style="`font-size: ${fontSize}`">{{ option.label ?? option.value }}</span>
<slot :name="`icon-right-${option.value}`"></slot>
</SelectItem>
</div>
<SelectItem
@click.prevent="pickOption(option.value)"
v-for="option of options"
v-for="option of optionsNoGroup"
:key="option.value"
:class="[
'option',
'flex',
{
firstOption: options[0].value === option.value,
lastOption: options[options.length - 1].value === option.value,
......@@ -111,7 +155,7 @@ const calcOptionColor = (color: TThemeColor | undefined, darknessColor: string |
:style="`color: ${calcOptionColor(option.color, option.darknessColor, textColor)};
background-color: ${calcOptionColor(option.background, option.darknessBackground, backgroundColor)}`"
:option="option"
:fontSize="fontSize"
:fontSizeNumber="fontSizeNumber"
:textColor="textColor"
>
<slot :name="`icon-left-${option.value}`"></slot>
......@@ -163,18 +207,35 @@ const calcOptionColor = (color: TThemeColor | undefined, darknessColor: string |
grid-template-rows: 1fr;
opacity: 1;
}
.option {
.group {
border-top: 1px solid;
}
.groupHeader {
cursor: auto;
}
.flexNoHover {
display: flex;
align-items: center;
gap: 5px;
padding: v-bind(padding);
}
.flex {
display: flex;
align-items: center;
gap: 5px;
padding: v-bind(padding);
}
.option:hover {
.flex:hover {
filter: brightness(90%);
transition: all 0.1s ease-in-out;
}
.group {
border-top: 1px solid v-bind(textColor);
border-bottom: 1px solid v-bind(textColor);
}
.filter {
cursor: auto;
gap: 7px;
}
.firstOption {
border-top-right-radius: 4px;
......
......@@ -5,7 +5,7 @@ import type { TThemeColor } from '@interfaces/common';
import { convertThemeToColor } from '@helpers/common';
interface IProps {
option: ISelectOption | undefined;
fontSize: string;
fontSizeNumber: string;
textColor: string;
}
defineProps<IProps>();
......@@ -19,14 +19,14 @@ const calcOptionColor = (color: TThemeColor | undefined, darknessColor: string |
<component
v-if="option?.iconLeft"
:is="iconsSet[option?.iconLeft]"
:size="fontSize.slice(0, -2)"
:size="fontSizeNumber"
:color="calcOptionColor(option?.iconLeftColor ?? option?.color, option?.darknessColor, textColor)"
/>
<slot />
<component
v-if="option?.iconRight"
:is="iconsSet[option?.iconRight]"
:size="fontSize.slice(0, -2)"
:size="fontSizeNumber"
:color="calcOptionColor(option?.iconRightColor ?? option?.color, option?.darknessColor, textColor)"
/>
<slot :name="`icon-right-${option?.value}`"></slot>
......
import type { TSize } from '@interfaces/common';
import type { ISelectGroup, ISelectOption } from '@interfaces/componentsProp';
export const getOptionsGroups = (options: ISelectOption[], groups: ISelectGroup[] | undefined, filter: string) => {
if (!groups?.length) return [];
const optionsWithGroup = options.filter(
(option) =>
option.group && (filter ? (option.label ?? option.value).toLowerCase().startsWith(filter.toLowerCase()) : true),
);
if (filter) groups = groups.filter((group) => optionsWithGroup.find((option) => option.group === group.name));
for (const group of groups) {
group.items = optionsWithGroup.filter((option) => option.group === group.name);
}
return groups;
};
export const calcFontSize = (size: TSize) => {
if (size === 'normal') return '16px';
if (size === 'large') return '20px';
if (size === 'huge') return '24px';
return '12px';
};
export const calcPadding = (size: TSize) => {
if (size === 'normal') return '6px';
if (size === 'large') return '10px';
if (size === 'huge') return '14px';
return '4px';
};
<script setup lang="ts">
interface Props {
color?: string;
size?: string | number;
}
defineProps<Props>();
</script>
<template>
<svg
:width="`${size ?? 40}px`"
:height="`${size ?? 40}px`"
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M17.0392 15.6244C18.2714 14.084 19.0082 12.1301 19.0082 10.0041C19.0082 5.03127 14.9769 1 10.0041 1C5.03127 1 1 5.03127 1 10.0041C1 14.9769 5.03127 19.0082 10.0041 19.0082C12.1301 19.0082 14.084 18.2714 15.6244 17.0392L21.2921 22.707C21.6828 23.0977 22.3163 23.0977 22.707 22.707C23.0977 22.3163 23.0977 21.6828 22.707 21.2921L17.0392 15.6244ZM10.0041 17.0173C6.1308 17.0173 2.99087 13.8774 2.99087 10.0041C2.99087 6.1308 6.1308 2.99087 10.0041 2.99087C13.8774 2.99087 17.0173 6.1308 17.0173 10.0041C17.0173 13.8774 13.8774 17.0173 10.0041 17.0173Z"
:fill="color ?? '#000000'"
/>
</svg>
</template>
<style scoped></style>
This diff is collapsed.
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment