diff --git a/TEMP-MIGRATION-CHANGELOG.md b/TEMP-MIGRATION-CHANGELOG.md index 8cf239e0f9..8096686e3a 100644 --- a/TEMP-MIGRATION-CHANGELOG.md +++ b/TEMP-MIGRATION-CHANGELOG.md @@ -118,3 +118,7 @@ export default defineConfig({ - `v-model` is now mapped to `modelValue` prop instead of `selected` prop. - Added type interface for `options` prop. + +### KMenu & KMenuItem + +- Added type interface for `items` prop. diff --git a/docs/.vuepress/config.ts b/docs/.vuepress/config.ts index e59e09073e..ffd815716d 100644 --- a/docs/.vuepress/config.ts +++ b/docs/.vuepress/config.ts @@ -106,6 +106,7 @@ export default defineUserConfig({ '/components/input', '/components/input-switch', '/components/label', + '/components/menu', '/components/modal', '/components/pagination', '/components/popover', diff --git a/docs/components/menu.md b/docs/components/menu.md new file mode 100644 index 0000000000..6145fa95a8 --- /dev/null +++ b/docs/components/menu.md @@ -0,0 +1,228 @@ +# Menu + +**KMenu** - Menu component + + + +```vue + +``` + +## Props + +### items + +An array of items to populate the menu with. The value passed for the `items` prop should adhere to this type interface: + +```ts +export interface MenuItem { + title: string + description?: string +} + +export interface KMenuItemType extends MenuItem { + expandable: boolean + type: 'string' | 'number' | 'divider' +} +``` + +Properties: + +- `title` - menu item label +- `description` - text displayed when `expandable` item is expanded +- `expandable` - boolean of whether or not this item is expandable +- `type` - supported values: `string`, `number`, `divider` + +> Note: `type='divider'` will insert an empty item that is just an `
`. + + + +```vue + + + +``` + +### width + +You can pass a `width` string for **KMenu**. Either the string `auto` or a string width, like `120`. + +By default the `width` is set to `284px`. + + + +```vue + +``` + +## KMenuItem + +**KMenu** generates a **KMenuItem** for each item in the `items` property. + +### Properties + +- `item` - the menu item content is built from this. + - Properties: + - `title` - menu item label + - `description` - text displayed when `expandable` item is expanded + - Interface: + ```ts + export interface MenuItem { + title: string + description?: string + } + ``` +- `type` - supported values: `string`, `number`, `divider` +- `expandable` - boolean of whether or not this item is expandable +- `lastMenuItem` - boolean of whether or not this is the last item in the menu (for styling) + +```vue + +``` + +### Item Slots + +- `itemTitle` - the title content for the menu item +- `itemBody` - the body content for the menu item + +```vue + + + + +``` + +## Slots + +- `body` - The body of the menu, we expect this to be an array of `KMenuItem` components. This should be used instead of the `items` property. +- `actionButton` - the button at the bottom of the menu + + + + + + +```vue + + + + +``` + + + + diff --git a/src/components/KMenu/KMenu.spec.ts b/src/components/KMenu/KMenu.spec.ts new file mode 100644 index 0000000000..3073e6d8ca --- /dev/null +++ b/src/components/KMenu/KMenu.spec.ts @@ -0,0 +1,62 @@ +// Import types for custom commands +/// + +import { mount } from '@cypress/vue' +import KMenu from '@/components/KMenu/KMenu.vue' +import type { KMenuItemType } from '@/components/KMenu/KMenu.vue' + +/** + * ALL TESTS MUST USE testMode: true + * We generate unique IDs for reference by aria properties. Test mode strips these out + * allowing for successful snapshot verification. + * props: { + * testMode: true + * } + */ + +const getItems = (count: number): KMenuItemType[] => { + const myItems = [] + + for (let i = 0; i < count; i++) { + myItems.push({ + title: 'Item ' + i, + type: Math.random() < 0.5 ? 'string' : 'number', + expandable: Math.random() < 0.5, + description: "The item's description for number " + i, + }) + } + + return myItems +} + +const customItem = { + title: 'Item #', + description: 'Cras aliquet auctor ex ut hendrerit. Donec sagittis est nec aliquet semper. Quisque feugiat metus orci, at ullamcorper odio molestie non. Nam dignissim sed ligula ut commodo.', + expandable: true, +} + +describe('KMenu', () => { + it('renders proper menu when using props', () => { + mount(KMenu, { + props: { + items: getItems(5), + testMode: true, + }, + }) + + cy.get('.k-menu').should('be.visible') + cy.get('.k-menu-item').should('have.length', 5) + }) + + it('shows chevron down icon', () => { + mount(KMenu, { + props: { + items: [customItem], + testMode: true, + }, + }) + + cy.get('.k-menu').should('be.visible') + cy.get('.k-menu-item .k-button .span-icon-container').should('be.visible') + }) +}) diff --git a/src/components/KMenu/KMenu.vue b/src/components/KMenu/KMenu.vue new file mode 100644 index 0000000000..afb8e813fb --- /dev/null +++ b/src/components/KMenu/KMenu.vue @@ -0,0 +1,130 @@ + + + + + diff --git a/src/components/KMenu/KMenuDivider.vue b/src/components/KMenu/KMenuDivider.vue new file mode 100644 index 0000000000..8b43eb31ac --- /dev/null +++ b/src/components/KMenu/KMenuDivider.vue @@ -0,0 +1,22 @@ + + + + + + diff --git a/src/components/KMenu/KMenuItem.vue b/src/components/KMenu/KMenuItem.vue new file mode 100644 index 0000000000..631adcd355 --- /dev/null +++ b/src/components/KMenu/KMenuItem.vue @@ -0,0 +1,182 @@ + + + + + diff --git a/src/components/index.ts b/src/components/index.ts index 01820b9e8b..e941d0339a 100644 --- a/src/components/index.ts +++ b/src/components/index.ts @@ -38,3 +38,5 @@ export { default as KInputSwitch } from './KInputSwitch/KInputSwitch.vue' export { default as KTabs } from './KTabs/KTabs.vue' export { default as KSlideout } from './KSlideout/KSlideout.vue' export { default as KSegmentedControl } from './KSegmentedControl/KSegmentedControl.vue' +export { default as KMenu } from './KMenu/KMenu.vue' +export { default as KMenuItem } from './KMenu/KMenuItem.vue'