diff --git a/.gitignore b/.gitignore index 31fe48f6..45f18911 100644 --- a/.gitignore +++ b/.gitignore @@ -31,3 +31,7 @@ coverage .npmrc storybook-static + +.claude/* + +tsconfig.app.tsbuildinfo \ No newline at end of file diff --git a/.storybook/preview-head.html b/.storybook/preview-head.html index d1ea29f5..5ac53501 100644 --- a/.storybook/preview-head.html +++ b/.storybook/preview-head.html @@ -1,35 +1,23 @@ - - - - - - - - - - + + + +<Description /> + +```javascript +import { MegaMenu } from 'primevue'; +``` + +## Варианты использования + +### Horizontal + +<Canvas of={MegaMenuStories.Horizontal} /> + +### Vertical + +<Canvas of={MegaMenuStories.Vertical} /> + +### Кастомный шаблон + +<Canvas of={MegaMenuStories.Custom} /> diff --git a/src/plugins/prime/stories/Menu/MegaMenu/MegaMenu.stories.js b/src/plugins/prime/stories/Menu/MegaMenu/MegaMenu.stories.js new file mode 100644 index 00000000..d7b79963 --- /dev/null +++ b/src/plugins/prime/stories/Menu/MegaMenu/MegaMenu.stories.js @@ -0,0 +1,141 @@ +import { + HorizontalTemplate, + VerticalTemplate, + CustomTemplate, +} from './MegaMenu.template'; + +export default { + title: 'Prime/Menu/MegaMenu', + parameters: { + docs: { + description: { + component: + 'Расширенное меню с поддержкой многоколоночных подменю. Поддерживает горизонтальную и вертикальную ориентацию.', + }, + story: { height: '300px' }, + }, + designTokens: { + prefix: '--p-megamenu', + }, + }, +}; + +export const Horizontal = { + render: HorizontalTemplate.bind({}), + parameters: { + docs: { + source: { + code: `<script setup> +import { ref } from 'vue'; + +const items = ref([ + { + label: 'Products', + icon: 'ti ti-box', + items: [ + [ + { + label: 'UI Components', + items: [ + { label: 'Form', icon: 'ti ti-forms' }, + { label: 'Button', icon: 'ti ti-hand-click' }, + ], + }, + ], + ], + }, + { label: 'Contact', icon: 'ti ti-mail', disabled: true }, +]); +</script> + +<template> + <MegaMenu :model="items" /> +</template>`, + }, + }, + }, +}; + +export const Vertical = { + render: VerticalTemplate.bind({}), + parameters: { + docs: { + source: { + code: `<script setup> +import { ref } from 'vue'; + +const items = ref([ + { + label: 'Products', + icon: 'ti ti-box', + items: [ + [ + { + label: 'UI Components', + items: [ + { label: 'Form', icon: 'ti ti-forms' }, + { label: 'Button', icon: 'ti ti-hand-click' }, + ], + }, + ], + ], + }, +]); +</script> + +<template> + <MegaMenu :model="items" orientation="vertical" /> +</template>`, + }, + }, + }, +}; + +export const Custom = { + render: CustomTemplate.bind({}), + parameters: { + docs: { + source: { + code: `<script setup> +import { ref } from 'vue'; +import MegaMenuItem from './MegaMenuItem.vue'; + +const items = ref([ + { + label: 'Products', + icon: 'ti ti-box', + items: [ + [ + { + label: 'Components', + items: [ + { + label: 'Form', + description: 'Input, Select, Checkbox', + icon: 'ti ti-forms', + badge: 'New', + }, + { + label: 'Button', + description: 'Actions and triggers', + icon: 'ti ti-hand-click', + }, + ], + }, + ], + ], + }, +]); +</script> + +<template> + <MegaMenu :model="items"> + <template #item="{ item, props }"> + <MegaMenuItem :item="item" :action-props="props.action" /> + </template> + </MegaMenu> +</template>`, + }, + }, + }, +}; diff --git a/src/plugins/prime/stories/Menu/MegaMenu/MegaMenu.template.js b/src/plugins/prime/stories/Menu/MegaMenu/MegaMenu.template.js new file mode 100644 index 00000000..16572862 --- /dev/null +++ b/src/plugins/prime/stories/Menu/MegaMenu/MegaMenu.template.js @@ -0,0 +1,154 @@ +import { MegaMenu } from 'primevue'; +import { ref } from 'vue'; +import MegaMenuItem from './MegaMenuItem.vue'; + +const baseItems = [ + { + label: 'Products', + icon: 'ti ti-box', + items: [ + [ + { + label: 'UI Components', + items: [ + { label: 'Form', icon: 'ti ti-forms' }, + { label: 'Button', icon: 'ti ti-hand-click' }, + { label: 'Table', icon: 'ti ti-table' }, + ], + }, + ], + [ + { + label: 'Charts', + items: [ + { label: 'Bar Chart', icon: 'ti ti-chart-bar' }, + { label: 'Line Chart', icon: 'ti ti-chart-line' }, + ], + }, + ], + ], + }, + { + label: 'Solutions', + icon: 'ti ti-bulb', + items: [ + [ + { + label: 'Business', + items: [ + { label: 'Analytics', icon: 'ti ti-chart-dots' }, + { label: 'CRM', icon: 'ti ti-users' }, + ], + }, + ], + ], + }, + { + label: 'Contact', + icon: 'ti ti-mail', + disabled: true, + }, +]; + +export const HorizontalTemplate = (args) => ({ + components: { MegaMenu }, + setup() { + const items = ref(baseItems); + + return { args, items }; + }, + template: `<MegaMenu :model="items" v-bind="args" />`, +}); + +export const VerticalTemplate = (args) => ({ + components: { MegaMenu }, + setup() { + const items = ref(baseItems); + + return { args, items }; + }, + template: `<MegaMenu :model="items" orientation="vertical" v-bind="args" />`, +}); + +export const CustomTemplate = (args) => ({ + components: { MegaMenu, MegaMenuItem }, + setup() { + const items = ref([ + { + label: 'Products', + icon: 'ti ti-box', + items: [ + [ + { + label: 'Components', + items: [ + { + label: 'Form', + description: 'Input, Select, Checkbox', + icon: 'ti ti-forms', + badge: 'New', + }, + { + label: 'Button', + description: 'Actions and triggers', + icon: 'ti ti-hand-click', + }, + ], + }, + ], + [ + { + label: 'Charts', + items: [ + { + label: 'Bar Chart', + description: 'Categorical comparison', + icon: 'ti ti-chart-bar', + }, + { + label: 'Line Chart', + description: 'Trends over time', + icon: 'ti ti-chart-line', + badge: 'Beta', + }, + ], + }, + ], + ], + }, + { + label: 'Solutions', + icon: 'ti ti-bulb', + items: [ + [ + { + label: 'Business', + items: [ + { + label: 'Analytics', + description: 'Reports and dashboards', + icon: 'ti ti-chart-dots', + }, + { + label: 'CRM', + description: 'Customer management', + icon: 'ti ti-users', + badge: 'Pro', + }, + ], + }, + ], + ], + }, + ]); + + return { args, items }; + }, + template: ` + <MegaMenu :model="items" v-bind="args"> + <template #item="{ item, props }"> + <MegaMenuItem :item="item" :action-props="props.action" /> + </template> + </MegaMenu> + `, +}); diff --git a/src/plugins/prime/stories/Menu/MegaMenu/MegaMenuItem.vue b/src/plugins/prime/stories/Menu/MegaMenu/MegaMenuItem.vue new file mode 100644 index 00000000..d4bfeb37 --- /dev/null +++ b/src/plugins/prime/stories/Menu/MegaMenu/MegaMenuItem.vue @@ -0,0 +1,44 @@ +<script setup lang="ts"> +import { Badge } from 'primevue'; + +defineProps({ + item: { + type: Object, + required: true, + }, + actionProps: { + type: Object, + default: () => ({}), + }, +}); +</script> + +<template> + <a v-bind="actionProps" class="p-megamenu-item-link"> + <span v-if="item.icon" :class="['p-megamenu-item-icon', item.icon]" /> + <div class="megamenu-item-label"> + <span class="p-megamenu-item-label">{{ item.label }}</span> + <small v-if="item.description" class="megamenu-item-caption">{{ + item.description + }}</small> + </div> + <Badge v-if="item.badge" :value="item.badge" /> + <span + v-if="item.items" + class="p-megamenu-submenu-icon ti ti-chevron-down" + /> + </a> +</template> + +<style scoped> +.megamenu-item-label { + display: flex; + flex-direction: column; + gap: var(--p-megamenu-extend-ext-item-caption-gap); +} + +.megamenu-item-caption { + font-size: var(--p-fonts-font-size-sm); + color: var(--p-megamenu-extend-ext-item-caption-color); +} +</style> diff --git a/src/plugins/prime/theme3.0/components/css/megamenu.ts b/src/plugins/prime/theme3.0/components/css/megamenu.ts new file mode 100644 index 00000000..06e5adc4 --- /dev/null +++ b/src/plugins/prime/theme3.0/components/css/megamenu.ts @@ -0,0 +1,31 @@ +const css = ({ dt }: { dt: (token: string) => string }) => ` + +.p-megamenu-submenu-icon, +.p-megamenu-item-icon { + font-size: ${dt('megamenu.extend.iconSize')}; +} + +.p-megamenu-item-label { + font-size: ${dt('fonts.fontSize.base')}; + font-weight: ${dt('fonts.fontWeight.regular')}; +} + +.p-megamenu-mobile-button-icon { + font-size: ${dt('megamenu.extend.iconSize')}; +} + + +/* Размер ширины панели по контенту и позиционирование для активных пунктов горизонтального вида от начала пункта меню */ +.p-megamenu-root-list > .p-megamenu-item-active > .p-megamenu-overlay, +.p-megamenu-vertical .p-megamenu-root-list > .p-megamenu-item-active > .p-megamenu-overlay { + min-width: fit-content; + left: unset; +} + +/* Позиционирование оверлея от пункта для вертикального вида */ +.p-megamenu.p-megamenu-vertical .p-megamenu-root-list > .p-megamenu-item-active > .p-megamenu-overlay { + left: 100%; +} +`; + +export default css;