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

feat: almost finish with Select but still without group's logic (and add one icon)

parent 2caf0a97
No related branches found
No related tags found
1 merge request!3Table (partially), Checkbox, Tag, Select and Knob
import type { StorybookConfig } from '@storybook/vue3-vite'
import type { StorybookConfig } from '@storybook/vue3-vite';
const config: StorybookConfig = {
stories: ['../src/**/*.stories.@(js|jsx|mjs|ts|tsx)'],
addons: [
'@storybook/addon-onboarding',
'@storybook/addon-essentials',
'@chromatic-com/storybook',
'@storybook/addon-interactions',
],
addons: ['@storybook/addon-essentials', '@chromatic-com/storybook', '@storybook/addon-interactions'],
framework: {
name: '@storybook/vue3-vite',
options: {},
},
}
export default config
};
export default config;
......@@ -23,7 +23,9 @@
## Общая информация
### Components count: 13
### Bundle size: 278.3KB
---
## Важные моменты при разработке
### Общее начало шаблона для компонентов:
```
......
......@@ -107,6 +107,7 @@ import UserIcon from '@stories/icons/Mono/UserIcon.vue';
import ArrowsVerticalIcon from '@stories/icons/Mono/ArrowsVerticalIcon.vue';
import TriangleIcon from '@stories/icons/Mono/TriangleIcon.vue';
import Playground from '@/Playground.vue';
import ArrowShortDownIcon from '@stories/icons/Mono/ArrowShortDownIcon.vue';
const gentleIcons = {
Age18Icon,
......@@ -121,6 +122,7 @@ const gentleIcons = {
ArrowForwardIcon,
ArrowLeftIcon,
ArrowRightIcon,
ArrowShortDownIcon,
ArrowsVerticalIcon,
AtIcon,
AwardIcon,
......
......@@ -15,6 +15,8 @@ import { ref } from 'vue';
import type { ISBOption, ISliderOptions, ITableColumn } from '@interfaces/componentsProp';
import Checkbox from '@stories/components/Checkbox/Checkbox.vue';
import Tag from '@stories/components/Tag/Tag.vue';
import Select from '@stories/components/Select/Select.vue';
import AtIcon from '@stories/icons/Mono/AtIcon.vue';
const visibleDrawer = ref(false);
const sliderOptions: ISliderOptions[] = [
......@@ -181,13 +183,25 @@ const tableData = [
],
];
const activeCheckbox = ref();
const selectOptions = [
{
value: 'First',
},
{
value: 'Second',
},
];
</script>
<template>
<h2 class="title gradient-text">Playground</h2>
<Select :options="selectOptions" theme="sky">
<template #icon-left-First>
<AtIcon color="#3aa" size="20" />
</template>
</Select>
<Tag theme="sky">
<template #icon-right><TrashIcon color="#3333aa" size="18" /></template>
<template #icon-left><TrashIcon color="sky" size="18" /></template>
</Tag>
{{ activeCheckbox }}
<Checkbox v-model:active="activeCheckbox" size="small" />
......
......@@ -107,113 +107,115 @@ import SortHorizontalIcon from '@stories/icons/Mono/SortHorizontalIcon.vue';
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';
export const iconsSet: Record<string, Component> = {
Age18Icon: Age18Icon,
AirplaneIcon: AirplaneIcon,
AlarmIcon: AlarmIcon,
AlignCenterIcon: AlignCenterIcon,
AlignLeftIcon: AlignLeftIcon,
AlignRightIcon: AlignRightIcon,
AnchorIcon: AnchorIcon,
AnchorLinkIcon: AnchorLinkIcon,
ArchiveIcon: ArchiveIcon,
ArrowForwardIcon: ArrowForwardIcon,
ArrowLeftIcon: ArrowLeftIcon,
ArrowRightIcon: ArrowRightIcon,
ArrowsVerticalIcon: ArrowsVerticalIcon,
AtIcon: AtIcon,
AwardIcon: AwardIcon,
BackspaceIcon: BackspaceIcon,
BadgeIcon: BadgeIcon,
BallFootballIcon: BallFootballIcon,
BallIcon: BallIcon,
BanknoteIcon: BanknoteIcon,
BellIcon: BellIcon,
BellOffIcon: BellOffIcon,
BoxIcon: BoxIcon,
BrightnessIcon: BrightnessIcon,
BulbIcon: BulbIcon,
CalculatorIcon: CalculatorIcon,
CalendarAddIcon: CalendarAddIcon,
CalendarIcon: CalendarIcon,
CalendarRemoveIcon: CalendarRemoveIcon,
CardsIcon: CardsIcon,
ChartLineIcon: ChartLineIcon,
ChartPieIcon: ChartPieIcon,
ChatIcon: ChatIcon,
ChatWritingIcon: ChatWritingIcon,
CheckMarkIcon: CheckMarkIcon,
ChemistryFlaskIcon: ChemistryFlaskIcon,
CodeIcon: CodeIcon,
CoinsIcon: CoinsIcon,
ColorPaletteIcon: ColorPaletteIcon,
CompassIcon: CompassIcon,
ConstructionWorkerIcon: ConstructionWorkerIcon,
ContactsIcon: ContactsIcon,
CropIcon: CropIcon,
CrossIcon: CrossIcon,
CrossCircleIcon: CrossCircleIcon,
CubeIcon: CubeIcon,
CupIcon: CupIcon,
CursorIcon: CursorIcon,
DiamondIcon: DiamondIcon,
DiaryIcon: DiaryIcon,
DiceIcon: DiceIcon,
DigIcon: DigIcon,
DislikeIcon: DislikeIcon,
DisplayIcon: DisplayIcon,
DocumentIcon: DocumentIcon,
DocumentAddIcon: DocumentAddIcon,
DocumentDeleteIcon: DocumentDeleteIcon,
DocumentEditIcon: DocumentEditIcon,
DollarIcon: DollarIcon,
DotsHorizontalIcon: DotsHorizontalIcon,
DotsVerticalIcon: DotsVerticalIcon,
DownloadIcon: DownloadIcon,
DropIcon: DropIcon,
DumbbelIcon: DumbbelIcon,
EarthIcon: EarthIcon,
EditIcon: EditIcon,
EncyclopediaIcon: EncyclopediaIcon,
ExitIcon: ExitIcon,
EyeIcon: EyeIcon,
FeedbackIcon: FeedbackIcon,
FilterIcon: FilterIcon,
FingerprintIcon: FingerprintIcon,
FireIcon: FireIcon,
FlagIcon: FlagIcon,
FlashIcon: FlashIcon,
FlashlightIcon: FlashlightIcon,
FolderLockIcon: FolderLockIcon,
FrameIcon: FrameIcon,
FullScreenIcon: FullScreenIcon,
GameControllerIcon: GameControllerIcon,
GiftIcon: GiftIcon,
GlassesIcon: GlassesIcon,
HamburgerIcon: HamburgerIcon,
HandIcon: HandIcon,
HomeIcon: HomeIcon,
ImageIcon: ImageIcon,
ImageEditIcon: ImageEditIcon,
LineIcon: LineIcon,
LineDashedIcon: LineDashedIcon,
LineDottedIcon: LineDottedIcon,
LineDiagonalIcon: LineDiagonalIcon,
MoveIcon: MoveIcon,
ParagraphIcon: ParagraphIcon,
PhoneHandsetIcon: PhoneHandsetIcon,
PlusCircleIcon: PlusCircleIcon,
PlusIcon: PlusIcon,
PointerIcon: PointerIcon,
SaveIcon: SaveIcon,
SettingsIcon: SettingsIcon,
Age18: Age18Icon,
Airplane: AirplaneIcon,
Alarm: AlarmIcon,
AlignCenter: AlignCenterIcon,
AlignLeft: AlignLeftIcon,
AlignRight: AlignRightIcon,
Anchor: AnchorIcon,
AnchorLink: AnchorLinkIcon,
Archive: ArchiveIcon,
ArrowForward: ArrowForwardIcon,
ArrowLeft: ArrowLeftIcon,
ArrowRight: ArrowRightIcon,
ArrowShortDown: ArrowShortDownIcon,
ArrowsVertical: ArrowsVerticalIcon,
At: AtIcon,
Award: AwardIcon,
Backspace: BackspaceIcon,
Badge: BadgeIcon,
BallFootball: BallFootballIcon,
Ball: BallIcon,
Banknote: BanknoteIcon,
Bell: BellIcon,
BellOff: BellOffIcon,
Box: BoxIcon,
Brightness: BrightnessIcon,
Bulb: BulbIcon,
Calculator: CalculatorIcon,
CalendarAdd: CalendarAddIcon,
Calendar: CalendarIcon,
CalendarRemove: CalendarRemoveIcon,
Cards: CardsIcon,
ChartLine: ChartLineIcon,
ChartPie: ChartPieIcon,
Chat: ChatIcon,
ChatWriting: ChatWritingIcon,
CheckMark: CheckMarkIcon,
ChemistryFlask: ChemistryFlaskIcon,
Code: CodeIcon,
Coins: CoinsIcon,
ColorPalette: ColorPaletteIcon,
Compass: CompassIcon,
ConstructionWorker: ConstructionWorkerIcon,
Contacts: ContactsIcon,
Crop: CropIcon,
Cross: CrossIcon,
CrossCircle: CrossCircleIcon,
Cube: CubeIcon,
Cup: CupIcon,
Cursor: CursorIcon,
Diamond: DiamondIcon,
Diary: DiaryIcon,
Dice: DiceIcon,
Dig: DigIcon,
Dislike: DislikeIcon,
Display: DisplayIcon,
Document: DocumentIcon,
DocumentAdd: DocumentAddIcon,
DocumentDelete: DocumentDeleteIcon,
DocumentEdit: DocumentEditIcon,
Dollar: DollarIcon,
DotsHorizontal: DotsHorizontalIcon,
DotsVertical: DotsVerticalIcon,
Download: DownloadIcon,
Drop: DropIcon,
Dumbbel: DumbbelIcon,
Earth: EarthIcon,
Edit: EditIcon,
Encyclopedia: EncyclopediaIcon,
Exit: ExitIcon,
Eye: EyeIcon,
Feedback: FeedbackIcon,
Filter: FilterIcon,
Fingerprint: FingerprintIcon,
Fire: FireIcon,
Flag: FlagIcon,
Flash: FlashIcon,
Flashlight: FlashlightIcon,
FolderLock: FolderLockIcon,
Frame: FrameIcon,
FullScreen: FullScreenIcon,
GameController: GameControllerIcon,
Gift: GiftIcon,
Glasses: GlassesIcon,
Hamburger: HamburgerIcon,
Hand: HandIcon,
Home: HomeIcon,
Image: ImageIcon,
ImageEdit: ImageEditIcon,
Line: LineIcon,
LineDashed: LineDashedIcon,
LineDotted: LineDottedIcon,
LineDiagonal: LineDiagonalIcon,
Move: MoveIcon,
Paragraph: ParagraphIcon,
PhoneHandset: PhoneHandsetIcon,
PlusCircle: PlusCircleIcon,
Plus: PlusIcon,
Pointer: PointerIcon,
Save: SaveIcon,
Settings: SettingsIcon,
SortDownIcon,
SortHorizontalIcon,
SortUpIcon,
SortVerticalIcon,
TableIcon: TableIcon,
TrashIcon: TrashIcon,
TriangleIcon: TriangleIcon,
UserIcon: UserIcon,
Table: TableIcon,
Trash: TrashIcon,
Triangle: TriangleIcon,
User: UserIcon,
};
......@@ -55,6 +55,25 @@ export interface IMDItemProps {
onClick?: () => void;
}
export interface ISelectOption {
value: string;
label?: string;
iconLeft?: TIcons;
iconRight?: TIcons;
iconColor?: TThemeColor;
color?: TThemeColor;
darknessColor?: TDarkness;
background?: TThemeColor;
darknessBackground?: TDarkness;
}
export interface ISelectGroup {
title: string;
iconLeft?: TIcons;
iconRight?: TIcons;
items: string[];
}
export interface ISBOption {
label: string;
value?: never;
......
......@@ -13,6 +13,8 @@ import type {
import type {
IMDItemProps,
ISBOption,
ISelectGroup,
ISelectOption,
ISliderOptions,
ITableColumn,
ITableItem,
......@@ -122,6 +124,21 @@ export interface IPopupProps {
left?: number;
}
export interface ISelectProps {
options: ISelectOption[];
groups?: ISelectGroup[];
width?: string;
placeholder?: string;
openIcon?: TIcons;
size?: TSize;
name?: string;
theme?: TThemeColor;
background?: TThemeColor;
darknessTheme?: TDarkness;
darknessBackground?: TDarkness;
disabled?: boolean;
}
export interface ISBProps {
options: ISBOption[];
size?: TSize;
......
import type { Meta, StoryObj } from '@storybook/vue3';
import Select from './Select.vue';
const meta: Meta = {
title: 'Components/Select',
component: Select,
tags: ['autodocs'],
parameters: {
docs: {
description: {
component: 'A component that is used as a Select. Can be used with icon.',
},
},
},
argTypes: {
width: { control: 'text' },
disabled: { control: 'boolean' },
placeholder: { control: 'text' },
name: { control: 'text' },
size: { control: 'select', options: ['small', 'normal', 'large', 'huge'] },
darknessTheme: { control: 'select', options: ['100', '200', '300', '400', '500', '600', '700', '800', '900'] },
darknessBackground: { control: 'select', options: ['100', '200', '300', '400', '500', '600', '700', '800', '900'] },
theme: {
control: 'select',
options: [
'white',
'blue',
'sky',
'cyan',
'teal',
'green',
'yellow',
'orange',
'pink',
'fuchsia',
'purple',
'indigo',
'rose',
'red',
'black',
],
},
background: {
control: 'select',
options: [
'white',
'blue',
'sky',
'cyan',
'teal',
'green',
'yellow',
'orange',
'pink',
'fuchsia',
'purple',
'indigo',
'rose',
'red',
'black',
],
},
},
} satisfies Meta<typeof Select>;
export default meta;
type Story = StoryObj<typeof meta>;
export const Simple: Story = {
args: {
options: [
{
value: 'First',
},
{
value: 'Second',
},
{
value: 'Third',
},
],
},
};
export const Full: Story = {
args: {
options: [
{
value: 'First',
iconLeft: 'At',
color: 'purple',
darknessColor: '800',
},
{
value: 'Second',
iconColor: 'red',
iconRight: 'Age18',
},
{
iconLeft: 'Calendar',
value: 'Third',
iconRight: 'CheckMark',
},
],
placeholder: 'Select a city',
size: 'normal',
width: '250px',
theme: 'sky',
background: 'sky',
darknessTheme: '700',
darknessBackground: '200',
},
};
<script setup lang="ts">
import type { ISelectProps } from '@interfaces/componentsProps';
import { computed, ref } from 'vue';
import { convertThemeToColor } from '@helpers/common';
import { iconsSet } from '@/common/constants/icons';
import type { TThemeColor } from '@interfaces/common';
const props = withDefaults(defineProps<ISelectProps>(), {
size: 'normal',
width: '200px',
theme: 'black',
darknessTheme: '700',
darknessBackground: '200',
name: 'select',
placeholder: 'Nothing selected',
openIcon: 'ArrowShortDown',
});
const selected = defineModel('value');
// watch(, () => {});
const isOpen = ref<boolean>(false);
const selectedOption = computed(() => props.options.find((option) => option.value === selected.value));
const textColor = computed(() => convertThemeToColor(props.theme, props.darknessTheme));
const backgroundColor = computed(() =>
convertThemeToColor(
props.background ?? (props.theme === 'white' ? 'black' : props.theme === 'black' ? 'white' : props.theme),
(!props.background && props.theme === 'black') || (props.background === 'white' && props.theme === 'black')
? '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 pickOption = (value: string) => {
selected.value = value;
isOpen.value = false;
};
const calcOptionColor = (color: TThemeColor | undefined, darknessColor: string | undefined, defaultColor: string) =>
color ? convertThemeToColor(color, darknessColor ?? '500') : defaultColor;
</script>
<template>
<section>
<select :name="name" id="select">
<option value=""></option>
<option v-for="option of options" :key="option.value" :selected="selected === option.value">
{{ option.label ?? option.value }}
</option>
</select>
<div class="list" :style="`background-color: ${backgroundColor}`">
<button @click.prevent="isOpen = !isOpen" class="button" :style="`width: ${width}`">
<span
class="selected"
:style="`color: ${selected ? calcOptionColor(selectedOption?.color, selectedOption?.darknessColor, textColor) : '#62708c'}; font-weight: 600`"
>
<slot :name="`icon-left-${selectedOption?.value}`"></slot>
<component
v-if="selectedOption?.iconLeft"
:is="iconsSet[selectedOption?.iconLeft]"
:size="fontSize.slice(0, -2)"
:color="
calcOptionColor(
selectedOption.iconColor ?? selectedOption?.color,
selectedOption?.darknessColor,
textColor,
)
" /><span>{{ selected ?? placeholder }}</span>
<component
v-if="selectedOption?.iconRight"
:is="iconsSet[selectedOption?.iconRight]"
:size="fontSize.slice(0, -2)"
:color="
calcOptionColor(
selectedOption.iconColor ?? selectedOption?.color,
selectedOption?.darknessColor,
textColor,
)
" /><slot :name="`icon-right-${selectedOption?.value}`"></slot></span
><component
:is="iconsSet[openIcon]"
:size="fontSize.slice(0, -2)"
color="#62708c"
:style="`width: ${fontSize}`"
/>
</button>
<div
:class="[
'options',
{
optionsOpened: isOpen,
},
]"
>
<div style="overflow: hidden">
<p
@click.prevent="pickOption(option.value)"
v-for="option of options"
:key="option.value"
:class="[
'option',
{
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)}`"
>
<slot :name="`icon-left-${option.value}`"></slot>
<component
v-if="option.iconLeft"
:is="iconsSet[option.iconLeft]"
:size="fontSize.slice(0, -2)"
:color="calcOptionColor(option.iconColor ?? option.color, option.darknessColor, textColor)"
/><span>{{ option.label ?? option.value }}</span
><component
v-if="option.iconRight"
:is="iconsSet[option.iconRight]"
:size="fontSize.slice(0, -2)"
:color="calcOptionColor(option.iconColor ?? option.color, option.darknessColor, textColor)"
/>
<slot :name="`icon-right-${option.value}`"></slot>
</p>
</div>
</div>
</div>
</section>
</template>
<style scoped>
#select {
display: none;
}
.list {
position: relative;
width: max-content;
border: 1px solid v-bind(textColor);
border-radius: 5px;
cursor: pointer;
}
.button {
display: flex;
padding: 7px;
justify-content: space-between;
gap: 10px;
}
.selected {
display: flex;
gap: 5px;
}
.options {
position: absolute;
z-index: 5000;
top: 101%;
width: 100%;
border: 1px solid v-bind(textColor);
border-radius: 5px;
display: grid;
grid-template-rows: 0fr;
opacity: 0;
transition:
all 0.2s ease-in-out,
opacity 0.1s ease-in-out;
}
.optionsOpened {
grid-template-rows: 1fr;
opacity: 1;
}
.option {
display: flex;
align-items: center;
gap: 5px;
padding: 7px;
}
.option:hover {
filter: brightness(90%);
transition: all 0.1s ease-in-out;
}
.group {
border-top: 1px solid v-bind(textColor);
}
.firstOption {
border-top-right-radius: 4px;
border-top-left-radius: 4px;
}
.lastOption {
border-bottom-right-radius: 4px;
border-bottom-left-radius: 4px;
}
</style>
<!--.list {-->
<!--position: relative;-->
<!--width: max-content;-->
<!--border: 1px solid v-bind(textColor);-->
<!--border-radius: 5px 5px 0 0;-->
<!--cursor: pointer;-->
<!--}-->
<!--.button {-->
<!--display: flex;-->
<!--padding: 7px;-->
<!--justify-content: space-between;-->
<!--gap: 10px;-->
<!--}-->
<!--.selected {-->
<!--display: flex;-->
<!--gap: 5px;-->
<!--}-->
<!--.options {-->
<!--position: absolute;-->
<!--top: 100%;-->
<!--width: 100%;-->
<!--border: 1px solid v-bind(textColor);-->
<!--border-top: none;-->
<!--border-bottom-left-radius: 5px;-->
<!--border-bottom-right-radius: 5px;-->
<!--display: grid;-->
<!--grid-template-rows: 0fr;-->
<!--opacity: 0;-->
<!--transition:-->
<!--all 0.2s ease-in-out,-->
<!--opacity 0.1s ease-in-out;-->
<!--}-->
......@@ -19,7 +19,9 @@ const backgroundColor = computed(() =>
props.darknessBackground,
),
);
const borderColor = computed(() => (props.border ? convertThemeToColor(props.border, props.darknessBorder) : ''));
const borderColor = computed(() =>
props.border ? convertThemeToColor(props.border, props.darknessBorder) : 'transparent',
);
const fontSize = computed(() => {
const size = props.size;
if (size === 'normal') return '16px';
......
<script setup lang="ts">
interface Props {
color?: string;
size?: string | number;
}
defineProps<Props>();
</script>
<template>
<svg
:width="size ?? '40px'"
:height="size ?? '40px'"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 300 300"
shape-rendering="geometricPrecision"
text-rendering="geometricPrecision"
>
<line
x1="-63.725864"
y1="-43.107899"
x2="62.725865"
y2="43.107899"
transform="matrix(1.066014 0 0 1.167066 83.13335 150)"
fill="none"
:stroke="color ?? '#000000'"
stroke-width="30"
stroke-linecap="round"
/>
<line
x1="-62.725864"
y1="-43.107899"
x2="62.725865"
y2="43.107899"
transform="matrix(-1.068571 0 0 1.167066 217.02704 150)"
fill="none"
:stroke="color ?? '#000000'"
stroke-width="30"
stroke-linecap="round"
/>
</svg>
</template>
<style scoped></style>
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