diff --git a/src/App.vue b/src/App.vue index 498670c87c4af9f71d1e8dada035f7ec83129a0d..e2f6e6327f7fef6790dc4f4329e1e748ff2203b3 100644 --- a/src/App.vue +++ b/src/App.vue @@ -103,9 +103,11 @@ import TableIcon from '@stories/icons/Mono/TableIcon.vue'; import TrashIcon from '@stories/icons/Mono/TrashIcon.vue'; import UserIcon from '@stories/icons/Mono/UserIcon.vue'; import ArrowsVerticalIcon from '@stories/icons/Mono/ArrowsVerticalIcon.vue'; -import ToggleButton from '@stories/components/ToggleButton/ToggleButton.vue'; +import SelectButton from '@stories/components/SelectButton/SelectButton.vue'; import ToggleSwitch from '@stories/components/ToggleSwitch/ToggleSwitch.vue'; import TriangleIcon from '@stories/icons/Mono/TriangleIcon.vue'; +import Button from '@stories/components/Button/Button.vue'; +import Slider from '@stories/components/Slider/Slider.vue'; const gentleIcons = [ Age18Icon, @@ -223,14 +225,77 @@ const options = [ label: 'Second', }, ]; +const sliderOptions = [ + { + label: 0, + value: 0, + color: 'red', + }, + { + label: 2, + value: 2, + color: 'orange', + }, + { + label: 4, + value: 4, + color: 'yellow', + }, + { + label: 6, + value: 6, + color: 'green', + }, + { + label: 8, + value: 8, + color: 'sky', + }, + { + label: 10, + value: 10, + color: 'purple', + }, + { + label: 12, + value: 12, + color: 'purple', + }, + { + label: 14, + value: 14, + color: 'purple', + }, + { + label: 16, + value: 16, + color: 'purple', + }, + { + label: 18, + value: 18, + color: 'purple', + }, +]; </script> <template> - <ToggleButton :options="options" size="large"> + <Slider + :options="sliderOptions" + width="400" + min="0" + max="18" + step="2" + backgroundColor="black" + theme="blue" + isSmooth + /> + <Button theme="sky" label="I'm a button"></Button> + <SelectButton :options="options" size="large"> <template #1Icon> <TrashIcon /> </template> - </ToggleButton> + </SelectButton> <ToggleSwitch /> <Drawer v-model:visible="visibleDrawer" theme="sky" closeIcon="CropIcon"> <template #header>Ðто - Drawer</template> diff --git a/src/assets/main.css b/src/assets/main.css index 940084e32655a9bf56fcaf0b1c6371844f4b8043..274ba60d3475885167410c8c22fab87d055a472b 100644 --- a/src/assets/main.css +++ b/src/assets/main.css @@ -148,6 +148,9 @@ body { min-height: 100vh; overflow-x: hidden; } +* { + font-family: Arial, serif; +} #app { min-height: 100vh; display: flex; diff --git a/src/common/interfaces/components.ts b/src/common/interfaces/components.ts index 8f276af242722c4146869eff087e154e271cdd92..9c101ffaa941f35295bbd69b1390adb75a7a1d6f 100644 --- a/src/common/interfaces/components.ts +++ b/src/common/interfaces/components.ts @@ -1,9 +1,14 @@ +import type { TIcons, TThemeColor } from '@interfaces/common'; + export interface ITreeItem { label: string; link?: string; - color?: string; - iconBefore?: string; - iconAfter?: string; - iconColor?: string; + linkBlank?: boolean; + color?: TThemeColor; + textStyle?: 'bold' | 'italic'; + isTriangleToColor?: boolean; + iconBefore?: TIcons; + iconAfter?: TIcons; + iconColor?: TThemeColor; children?: ITreeItem[]; } diff --git a/src/stories/components/Button/Button.stories.ts b/src/stories/components/Button/Button.stories.ts index 9d64adc21831ca72fd632d0cc3e63e6860d90ddc..694b585956c357e78dd9355805b405d43519fe59 100644 --- a/src/stories/components/Button/Button.stories.ts +++ b/src/stories/components/Button/Button.stories.ts @@ -9,7 +9,7 @@ const meta: Meta = { parameters: { docs: { description: { - component: 'A component that is used to select a value from a list using a button.', + component: 'A component that is used as a button. Can be used with icon.', }, }, }, diff --git a/src/stories/components/Button/Button.vue b/src/stories/components/Button/Button.vue index 63b95ba295982bd18542a336fef9cb02089f13fd..fd21f80b8b438fae14109c9ca179cb4d67f566e3 100644 --- a/src/stories/components/Button/Button.vue +++ b/src/stories/components/Button/Button.vue @@ -6,13 +6,14 @@ import { convert500ThemeToColor } from '@helpers/colors'; const props = withDefaults( defineProps<{ label?: string; - size?: 'small' | 'medium' | 'large' | 'extraLarge'; + size?: 'small' | 'medium' | 'large' | 'huge'; textStyle?: 'bold' | 'italic'; iconPos?: 'left' | 'top' | 'right' | 'bottom'; width?: string | number; theme?: TThemeColor; textColor?: TThemeColor; border?: TThemeColor; + iconOnly?: boolean; }>(), { size: 'medium', @@ -30,8 +31,8 @@ const textSize = computed(() => { return '12px'; case 'large': return '20px'; - case 'extraLarge': - return '24px'; + case 'huge': + return '28px'; } return '16px'; }); @@ -41,7 +42,7 @@ const buttonPadding = computed(() => { return '0.5rem 0.375rem'; case 'large': return '1.2rem 0.8rem'; - case 'extraLarge': + case 'huge': return '1.8rem 1.2rem'; } return '0.75rem 0.5rem'; @@ -62,6 +63,7 @@ const width = computed(() => (props.width ? `${props.width}px` : 'max-content')) > <span :style="`background-color: ${themeColor}`" class="background"></span> <span + v-if="label || !iconOnly" :style="`color: ${textColor}; font-size: ${textSize}`" :class="[ 'text', @@ -70,7 +72,7 @@ const width = computed(() => (props.width ? `${props.width}px` : 'max-content')) italic: textStyle === 'italic', }, ]" - >{{ label ?? 'Button' }}</span + >{{ label ? label : !iconOnly ? 'Button' : '' }}</span > <span v-if="$slots.default" @@ -99,6 +101,9 @@ const width = computed(() => (props.width ? `${props.width}px` : 'max-content')) .button:hover .background { filter: brightness(90%); } +.button:active .background { + filter: brightness(75%); +} .background { width: 100%; height: 100%; diff --git a/src/stories/components/ToggleButton/ToggleButton.stories.ts b/src/stories/components/SelectButton/SelectButton.stories.ts similarity index 93% rename from src/stories/components/ToggleButton/ToggleButton.stories.ts rename to src/stories/components/SelectButton/SelectButton.stories.ts index 64c15f21599905c006b7e2c411b35fa01a3b44b8..9b19e46eb06916e6445ffffdcd28e6126b8adc5c 100644 --- a/src/stories/components/ToggleButton/ToggleButton.stories.ts +++ b/src/stories/components/SelectButton/SelectButton.stories.ts @@ -1,11 +1,11 @@ import type { Meta, StoryObj } from '@storybook/vue3'; -import ToggleButton from './ToggleButton.vue'; +import SelectButton from './SelectButton.vue'; import { fn } from '@storybook/test'; const meta: Meta = { - title: 'Components/ToggleButton', - component: ToggleButton, + title: 'Components/SelectButton', + component: SelectButton, tags: ['autodocs'], parameters: { docs: { @@ -67,7 +67,7 @@ const meta: Meta = { // Use `fn` to spy on the onClick arg, which will appear in the actions panel once invoked: https://storybook.js.org/docs/essentials/actions#action-args onClick: fn(), }, -} satisfies Meta<typeof ToggleButton>; +} satisfies Meta<typeof SelectButton>; export default meta; diff --git a/src/stories/components/ToggleButton/ToggleButton.vue b/src/stories/components/SelectButton/SelectButton.vue similarity index 98% rename from src/stories/components/ToggleButton/ToggleButton.vue rename to src/stories/components/SelectButton/SelectButton.vue index 4367a1caa6c92929b323a842708781d1708054f2..4a99fb897c307ef7d458dbaa19e11e453e43cfd4 100644 --- a/src/stories/components/ToggleButton/ToggleButton.vue +++ b/src/stories/components/SelectButton/SelectButton.vue @@ -152,6 +152,9 @@ const buttonHeight = computed(() => { .button:hover .background { filter: brightness(90%); } +.button:active .background { + filter: brightness(75%); +} .background { width: 100%; height: 100%; diff --git a/src/stories/components/Slider/Slider.stories.ts b/src/stories/components/Slider/Slider.stories.ts new file mode 100644 index 0000000000000000000000000000000000000000..21452cc09b0dbba94677a308be30192cf5ab5196 --- /dev/null +++ b/src/stories/components/Slider/Slider.stories.ts @@ -0,0 +1,143 @@ +import type { Meta, StoryObj } from '@storybook/vue3'; + +import Slider from './Slider.vue'; +import type { TThemeColor } from '@interfaces/common'; + +const meta: Meta = { + title: 'Components/Slider', + component: Slider, + tags: ['autodocs'], + parameters: { + docs: { + description: { + component: 'A component to provide input with a drag handle.', + }, + }, + }, + argTypes: { + options: { control: 'array' }, + width: { control: 'text' }, + min: { control: 'text' }, + max: { control: 'text' }, + step: { control: 'text' }, + size: { control: 'select', options: ['small', 'medium', 'large', 'huge'] }, + orientation: { control: 'select', options: ['horizontal', 'vertical'] }, + isSmooth: { control: 'boolean' }, + theme: { + control: 'select', + options: [ + 'white', + 'slate', + 'blue', + 'sky', + 'teal', + 'green', + 'yellow', + 'orange', + 'pink', + 'fuchsia', + 'purple', + 'indigo', + 'rose', + 'red', + 'black', + ], + }, + backgroundColor: { + control: 'select', + options: [ + 'white', + 'slate', + 'blue', + 'sky', + 'teal', + 'green', + 'yellow', + 'orange', + 'pink', + 'fuchsia', + 'purple', + 'indigo', + 'rose', + 'red', + 'black', + ], + }, + }, + args: { + // primary: false, + // Use `fn` to spy on the onClick arg, which will appear in the actions panel once invoked: https://storybook.js.org/docs/essentials/actions#action-args + }, +} satisfies Meta<typeof Slider>; + +export default meta; + +type Story = StoryObj<typeof meta>; + +export const Primary: Story = { + args: {}, +}; + +export const Full: Story = { + args: { + min: '0', + max: '10', + step: '2', + size: 'small', + backgroundColor: 'black', + theme: 'blue', + isSmooth: true, + options: [ + { + label: 0, + value: 0, + color: 'red', + }, + { + label: 2, + value: 2, + color: 'orange', + }, + { + label: 4, + value: 4, + color: 'yellow', + }, + { + label: 6, + value: 6, + color: 'green', + }, + { + label: 8, + value: 8, + color: 'sky', + }, + { + label: 10, + value: 10, + color: 'purple', + }, + { + label: 12, + value: 12, + color: 'purple', + }, + { + label: 14, + value: 14, + color: 'purple', + }, + { + label: 16, + value: 16, + color: 'purple', + }, + { + label: 18, + value: 18, + color: 'purple', + }, + ], + }, +}; diff --git a/src/stories/components/Slider/Slider.vue b/src/stories/components/Slider/Slider.vue index 6219be056d78278f563d22d5a028900d5c372b8f..a689cbc765f882833618b57f9ded3b10625871dd 100644 --- a/src/stories/components/Slider/Slider.vue +++ b/src/stories/components/Slider/Slider.vue @@ -1,32 +1,37 @@ <script setup lang="ts"> -import { useVModel } from '@vueuse/core'; -import { computed } from 'vue'; -import { convertThemeToColorBlackDefault, convertThemeToColorWhiteDefault } from './helpers/index'; +import { computed, ref, watch } from 'vue'; +import type { TThemeColor } from '@interfaces/common'; +import { convert500ThemeToColor } from '@helpers/colors'; -interface Props { - value: string | number; - width?: string | number; - min?: string | number; - max?: string | number; - step?: string | number; - size?: 'small' | 'medium' | 'large' | 'extraLarge'; - theme?: string; - backgroundColor?: string; - orientation?: 'horizontal' | 'vertical'; - isSmooth?: any; - options?: { - label: string; - value: number; - color?: string; - }[]; -} -const props = defineProps<Props>(); -const emit = defineEmits(['update:value']); -const value = useVModel(props, 'value', emit); +const props = withDefaults( + defineProps<{ + width?: string | number; + min?: string | number; + max?: string | number; + step?: string | number; + size?: 'small' | 'medium' | 'large' | 'huge'; + theme?: TThemeColor; + backgroundColor?: TThemeColor; + orientation?: 'horizontal' | 'vertical'; + isSmooth?: boolean; + options?: { + label: string; + value: number; + color?: string; + }[]; + }>(), + { + width: '200', + size: 'medium', + theme: 'white', + backgroundColor: 'black', + }, +); +const value = defineModel('value'); const optionValue = ref( typeof value.value === 'string' ? props.options!.findIndex((option) => option.label === value.value) - : value.value + : value.value, ); watch([optionValue], () => { if (props.options) { @@ -42,23 +47,21 @@ watch([value], () => { } }); const sliderButtonSize = computed(() => { - if (!props.size) return '40px'; switch (props.size) { case 'small': return '25px'; - case 'medium': - return '40px'; case 'large': return '70px'; - case 'extraLarge': + case 'huge': return '100px'; } + return '40px'; }); const sliderHeight = computed(() => `${Math.floor(sliderButtonSize.value.slice(0, -2) / 3)}px`); -const sliderBorderRadius = props.isSmooth ? sliderHeight.value : '0%'; -const sliderButtonBorderRadius = props.isSmooth ? '50%' : '0%'; -const themeColor = computed(() => convertThemeToColorWhiteDefault(props.theme)); -const themeBackground = computed(() => convertThemeToColorBlackDefault(props.backgroundColor)); +const sliderBorderRadius = computed(() => (props.isSmooth ? sliderHeight.value : '0%')); +const sliderButtonBorderRadius = computed(() => (props.isSmooth ? '50%' : '0%')); +const themeColor = computed(() => convert500ThemeToColor(props.theme)); +const themeBackground = computed(() => convert500ThemeToColor(props.backgroundColor)); </script> <template> @@ -66,10 +69,10 @@ const themeBackground = computed(() => convertThemeToColorBlackDefault(props.bac :class="[ 'slideContainer', { - verticalSlider: orientation === 'vertical' - } + verticalSlider: orientation === 'vertical', + }, ]" - :style="`width: ${width ?? 200}px`" + :style="`width: ${width}px`" > <input v-model="optionValue" @@ -90,8 +93,8 @@ const themeBackground = computed(() => convertThemeToColorBlackDefault(props.bac :class="[ 'values', { - datalistVertical: orientation === 'vertical' - } + datalistVertical: orientation === 'vertical', + }, ]" > <template v-for="option of options" :key="option.value"> diff --git a/src/stories/components/TreeList/TreeItems.vue b/src/stories/components/TreeList/TreeItems.vue new file mode 100644 index 0000000000000000000000000000000000000000..68ebbe77e9059ae44e872fc2c09bc0f344a7b870 --- /dev/null +++ b/src/stories/components/TreeList/TreeItems.vue @@ -0,0 +1,154 @@ +<script setup lang="ts"> +import { iconsSet } from '@/common/constants/icons'; +import TriangleIcon from '@stories/icons/Mono/TriangleIcon.vue'; +import type { ITreeItem } from '@interfaces/components'; +import type { TThemeColor } from '@interfaces/common'; +import { convert500ThemeToColor } from '@helpers/colors'; + +defineProps<{ + state: { + isOpen: boolean; + label: string; + }[]; + items: ITreeItem[]; + textColor: TThemeColor; +}>(); +const emit = defineEmits(['toggleIsOpen', 'onClick']); +</script> + +<template> + <ul> + <li + v-for="item of items" + :key="item.label" + :class="[ + 'item', + { + pl27: !item.children, + openContent: state.find( + (itemState) => itemState.label === item.label && itemState.isOpen, + ), + }, + ]" + > + <article> + <section + :class="[ + 'textContainer', + { + pointer: item.children, + }, + ]" + @click="emit('toggleIsOpen', item)" + > + <TriangleIcon + v-if="item.children" + :class="[ + 'openButton', + { + openButtonOpened: state.find( + (itemState) => itemState.label === item.label && itemState.isOpen, + ), + }, + ]" + :color=" + item.color && item.isTriangleToColor ? convert500ThemeToColor(item.color) : textColor + " + size="17" + /> + <component + :is="iconsSet[item.iconBefore]" + v-if="item.iconBefore" + :color="convert500ThemeToColor(item.iconColor)" + style="min-width: 17px" + size="17" + /> + <a + :href="item.link" + :target="item.linkBlank ? '_blank' : '_self'" + :class="[ + 'label', + { + bold: item.textStyle === 'bold', + italic: item.textStyle === 'italic', + }, + ]" + :style="`color: ${item.color ? convert500ThemeToColor(item.color) : textColor}`" + @click="emit('onClick', item.link)" + >{{ item.label }}</a + > + <component + :is="iconsSet[item.iconAfter]" + v-if="item.iconAfter" + :color="convert500ThemeToColor(item.iconColor)" + style="min-width: 17px" + size="17" + /> + </section> + <section class="children"> + <TreeItems + :items="item.children" + :state="state" + :textColor="textColor" + @toggleIsOpen="emit('toggleIsOpen', $event)" + /> + </section> + </article> + </li> + </ul> +</template> + +<style scoped> +.item { + width: 100%; + transition: all 0.4s ease; + background-color: v-bind(themeColor); +} +.label { + display: inline-block; + position: relative; + padding: 4px 5px; + background-color: v-bind(themeColor); + word-break: break-word; +} +.openButton { + margin-right: 10px; + min-width: 17px; + transition: transform 0.3s ease; +} +.openButtonOpened { + transform: rotate(180deg); +} +.children { + width: 100%; + padding-left: 15px; + display: grid; + grid-template-rows: 0fr; + transition: + all 0.3s ease-in, + grid-template-rows 0.3s ease-out; +} +.children > ul { + overflow: hidden; +} +.openContent > article > .children { + grid-template-rows: 1fr; + opacity: 1; +} +.textContainer { + position: relative; + display: flex; +} +.pointer { + cursor: pointer; +} +.pl27 { + padding-left: 27px; +} +.bold { + font-weight: bold; +} +.italic { + font-style: italic; +} +</style> diff --git a/src/stories/components/TreeList/TreeList.stories.ts b/src/stories/components/TreeList/TreeList.stories.ts index 9a0d9e4c65a3f60bbffea616d4f427b1c23296b5..5ccd8611aac00594a82192fab2228fd07d66a66c 100644 --- a/src/stories/components/TreeList/TreeList.stories.ts +++ b/src/stories/components/TreeList/TreeList.stories.ts @@ -9,7 +9,8 @@ const meta: Meta = { parameters: { docs: { description: { - component: 'A component that is used to select a value from a list using a TreeList.', + component: + 'A component that is used to display hierarchical data. Can contain links to a new page or the same.', }, }, }, @@ -17,7 +18,6 @@ const meta: Meta = { items: { control: 'array' }, maxWidth: { control: 'number' }, expand: { control: 'boolean' }, - disabled: { control: 'boolean' }, theme: { control: 'select', options: [ @@ -60,6 +60,24 @@ export const Primary: Story = { children: [ { label: '1-1-1', + children: [ + { + label: '1-1-1-1', + children: [ + { + label: '1-1-1-1-1', + children: [ + { label: '1-1-1-1-1-1' }, + { label: '1-1-1-1-1-2' }, + { label: '1-1-1-1-1-3' }, + { label: '1-1-1-1-1-4' }, + ], + }, + ], + }, + { label: '1-1-1-2' }, + { label: '1-1-1-3' }, + ], }, { label: '1-1-2', @@ -84,5 +102,66 @@ export const Primary: Story = { }; export const Full: Story = { - args: {}, + args: { + items: [ + { + label: 'Font-family (mdn web docs)', + color: 'green', + iconBefore: 'EncyclopediaIcon', + iconColor: 'green', + link: 'https://developer.mozilla.org/en-US/docs/Web/CSS/font-family', + linkBlank: true, + textStyle: 'bold', + children: [ + { + label: 'Font-family (mdn web docs) but same page (error?)', + link: 'https://developer.mozilla.org/en-US/docs/Web/CSS/font-family', + color: 'sky', + iconBefore: 'EyeIcon', + iconColor: 'white', + textStyle: 'italic', + isTriangleToColor: true, + children: [ + { + label: '1-1-1', + color: 'red', + iconAfter: 'DiceIcon', + iconColor: 'red', + children: [ + { + label: '1-1-1-1', + children: [ + { label: '1-1-1-1-1' }, + { label: '1-1-1-1-2' }, + { label: '1-1-1-1-3' }, + { label: '1-1-1-1-4' }, + ], + }, + { label: '1-1-1-2' }, + { label: '1-1-1-3' }, + ], + }, + { + label: '1-1-2', + }, + ], + }, + { + label: '1-2', + }, + ], + }, + { + label: 'Second', + children: [ + { + label: '2-1', + }, + ], + }, + ], + + expand: true, + theme: 'black', + }, }; diff --git a/src/stories/components/TreeList/TreeList.vue b/src/stories/components/TreeList/TreeList.vue index bc6f9123a205426e972cfa512bf9343256207700..d13489d3e9d38b15c65a88fc6f8ebea4c0a73816 100644 --- a/src/stories/components/TreeList/TreeList.vue +++ b/src/stories/components/TreeList/TreeList.vue @@ -2,9 +2,8 @@ import { computed, ref, watch } from 'vue'; import type { ITreeItem } from '@interfaces/components'; import type { TThemeColor } from '@interfaces/common'; -import { iconsSet } from '@/common/constants/icons'; import { convert500ThemeToColor } from '@helpers/colors'; -import TriangleIcon from '@stories/icons/Mono/TriangleIcon.vue'; +import TreeItems from '@stories/components/TreeList/TreeItems.vue'; const props = withDefaults( defineProps<{ @@ -12,12 +11,10 @@ const props = withDefaults( maxWidth?: number; expand?: boolean; theme?: TThemeColor; - disabled?: boolean; }>(), { theme: 'white', maxWidth: 300, - disabled: false, }, ); const emit = defineEmits(['onClick']); @@ -30,31 +27,21 @@ const textColor = computed(() => { }); const state = ref([]); -const setInitialState = () => { - if (!props?.items) return; - for (const item of props.items) { +const setItemChildrenToState = (items: ITreeItem[]) => { + for (const item of items) { state.value.push({ isOpen: props?.expand, label: item.label, }); if (item.children) { - for (const child of item.children) { - state.value.push({ - isOpen: props?.expand, - label: child.label, - }); - if (child.children) { - for (const grandChild of child.children) { - state.value.push({ - isOpen: props?.expand, - label: grandChild.label, - }); - } - } - } + setItemChildrenToState(item.children); } } }; +const setInitialState = () => { + if (!props?.items) return; + setItemChildrenToState(props.items); +}; watch( [items], () => { @@ -71,206 +58,18 @@ const toggleIsOpen = (item) => </script> <template> - <ul :style="`background-color: ${themeColor ?? 'white'}; max-width: ${maxWidth}px`" class="tree"> - <li - v-for="item of items" - :key="item.label" - :class="[ - 'item', - { - openContent: state.find( - (itemState) => itemState.label === item.label && itemState.isOpen, - ), - }, - ]" - > - <section - :class="[ - 'textContainer', - { - pointer: !disabled && item.children, - }, - ]" - > - <TriangleIcon - v-if="item.children && !disabled" - :class="[ - 'openButton', - { - openButtonOpened: state.find( - (itemState) => itemState.label === item.label && itemState.isOpen, - ), - }, - ]" - :color="textColor" - size="15" - @click.prevent="toggleIsOpen(item)" - /> - <component - :is="iconsSet[item.iconBefore]" - v-if="item.iconBefore" - :color="item.iconColor" - size="20" - /> - <a :href="item.link" class="label" @click.prevent="emit('onClick', item.link)">{{ - item.label - }}</a> - <component - :is="iconsSet[item.iconAfter]" - v-if="item.iconAfter" - :color="item.iconColor" - size="20" - /> - </section> - <div class="children"> - <div - v-for="child of item.children" - :key="child.label" - :class="[ - 'item', - { - pl50: !child.children, - openContent: state.find( - (itemState) => itemState.label === child.label && itemState.isOpen, - ), - }, - ]" - > - <section - :class="[ - 'textContainer', - { - pointer: !disabled && child.children, - }, - ]" - > - <TriangleIcon - v-if="child.children && !disabled" - :class="[ - 'openButton', - { - openButtonOpened: state.find( - (itemState) => itemState.label === child.label && itemState.isOpen, - ), - }, - ]" - :color="textColor" - size="15" - @click.prevent="toggleIsOpen(child)" - /> - <component - :is="iconsSet[child.iconBefore]" - v-if="child.iconBefore" - :color="child.iconColor" - size="20" - /> - <a :href="child.link" class="label" @click.prevent="emit('onClick', item.link)">{{ - child.label - }}</a> - <component - :is="iconsSet[child.iconAfter]" - v-if="child.iconAfter" - :color="child.iconColor" - size="20" - /> - </section> - <div class="children"> - <div - v-for="grandChild of child.children" - :key="grandChild.label" - :class="[ - 'item', - { - pl50: !grandChild.children, - openContent: state.find( - (itemState) => itemState.label === grandChild.label && itemState.isOpen, - ), - }, - ]" - > - <div class="textContainer"> - <component - :is="iconsSet[grandChild.iconBefore]" - v-if="grandChild.iconBefore" - :color="grandChild.iconColor" - size="20" - /> - <p - :href="grandChild.link" - class="label" - @click.prevent="emit('onClick', item.link)" - > - {{ grandChild.label }} - </p> - <component - :is="iconsSet[grandChild.iconAfter]" - v-if="grandChild.iconAfter" - :color="grandChild.iconColor" - size="20" - /> - </div> - </div> - </div> - </div> - </div> - </li> - </ul> + <div + :style="`background-color: ${themeColor ?? 'white'}; max-width: ${maxWidth}px; padding: 15px 25px 15px 15px`" + class="tree" + > + <TreeItems + :items="items" + :state="state" + :textColor="textColor" + @toggleIsOpen="toggleIsOpen" + @onClick="emit('onClick')" + /> + </div> </template> -<style scoped> -.tree { - padding: 15px 25px 15px 15px; -} -.item { - width: 100%; - padding-left: 20px; - overflow: hidden; - transition: all 0.3s ease; - background-color: v-bind(themeColor); -} -.label { - display: inline-block; - position: relative; - padding: 4px 0; - z-index: 3; - color: v-bind(textColor); - background-color: v-bind(themeColor); - word-break: break-word; -} -.openButton { - margin-right: 15px; - transition: transform 0.3s ease; -} -.openButtonOpened { - transform: rotate(180deg); -} -.children { - width: 100%; - padding-left: 25px; - opacity: 0; - max-height: 0; - transform: translateY(-100%); - transition: all 0.3s ease; -} -.openContent > .children { - transform: translateY(0); - opacity: 1; - max-height: 1000px; -} -.textContainer { - position: relative; - display: flex; - gap: 10px; -} -.flex { - display: flex; - align-items: start; - justify-content: end; -} -.pointer { - cursor: pointer; -} -.pl50 { - padding-left: 50px; -} -</style> +<style scoped></style>