forked from pkp/ui-library
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
pkp/pkp-lib#10033 Add DropdownMenu component
- Loading branch information
1 parent
5f6b1c7
commit 4203f00
Showing
3 changed files
with
235 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
import {Primary, Controls, Stories, Meta, Story} from '@storybook/blocks'; | ||
import DropdownMenu from './DropdownMenu.vue'; | ||
|
||
import * as DropdownMenuStories from './DropdownMenu.stories.js'; | ||
|
||
<Meta of={DropdownMenuStories} /> | ||
|
||
# Dropdown Menu | ||
|
||
## Usage | ||
|
||
This component renders a dropdown menu that can display a list of actions. If the `name` prop is supplied, it is used as the button's label; otherwise, an ellipsis (`...`) is used. | ||
|
||
<Primary /> | ||
<Controls /> | ||
<Stories /> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,124 @@ | ||
import DropdownMenu from './DropdownMenu.vue'; | ||
|
||
export default { | ||
title: 'Components/DropdownMenu', | ||
component: DropdownMenu, | ||
render: (args) => ({ | ||
components: {DropdownMenu}, | ||
setup() { | ||
return {args}; | ||
}, | ||
template: '<DropdownMenu v-bind="args" />', | ||
}), | ||
argTypes: { | ||
actions: { | ||
description: | ||
'An array of action objects. Each object should contain `label` (string), `url` (string), an optional `icon` (string) and `isWarnable` (boolean) if the button needs the "warning" button styling from `<Button>` component.', | ||
table: { | ||
type: {summary: 'Array'}, | ||
defaultValue: {summary: '[]'}, | ||
}, | ||
defaultValue: [], | ||
}, | ||
name: { | ||
control: {type: 'text'}, | ||
description: | ||
'The name of the dropdown menu component (optional). If not supplied, then the dropdown menu will use an ellipsis menu (`...`)', | ||
}, | ||
position: { | ||
control: {type: 'select'}, | ||
options: ['left', 'right'], | ||
description: | ||
'Determines where to show the dropdown button. Options include `left` and `right`.', | ||
}, | ||
}, | ||
}; | ||
|
||
const downloadActions = [ | ||
{ | ||
label: 'Author-Only Sections Displayed (PDF)', | ||
url: '#', | ||
}, | ||
{ | ||
label: 'Author-Only Sections Displayed (XML)', | ||
url: '#', | ||
}, | ||
{ | ||
label: 'Editor Forms Shows All Review Sections (PDF)', | ||
url: '#', | ||
}, | ||
{ | ||
label: 'Editor Forms Shows All Review Sections (XML)', | ||
url: '#', | ||
}, | ||
]; | ||
|
||
export const Default = { | ||
args: { | ||
actions: downloadActions, | ||
name: 'Download Review Form', | ||
}, | ||
decorators: [ | ||
() => ({ | ||
template: '<div style="height: 200px"><story/></div>', | ||
}), | ||
], | ||
}; | ||
|
||
export const EllipsisMenu = { | ||
args: { | ||
actions: [ | ||
{ | ||
label: 'View', | ||
url: '#', | ||
icon: 'View', | ||
}, | ||
{ | ||
label: 'Email', | ||
url: '#', | ||
icon: 'Email', | ||
}, | ||
{ | ||
label: 'Login As', | ||
url: '#', | ||
icon: 'LoginAs', | ||
}, | ||
{ | ||
label: 'Remove User', | ||
url: '#', | ||
icon: 'Cancel', | ||
isWarnable: true, | ||
}, | ||
{ | ||
label: 'Disable User', | ||
url: '#', | ||
icon: 'DisableUser', | ||
isWarnable: true, | ||
}, | ||
{ | ||
label: 'Merge User', | ||
url: '#', | ||
icon: 'MergeUser', | ||
}, | ||
], | ||
name: undefined, | ||
}, | ||
decorators: [ | ||
() => ({ | ||
template: '<div style="height: 270px"><story/></div>', | ||
}), | ||
], | ||
}; | ||
|
||
export const LeftPosition = { | ||
args: { | ||
actions: downloadActions, | ||
name: 'Left Dropdown', | ||
position: 'left', | ||
}, | ||
decorators: [ | ||
() => ({ | ||
template: '<div style="height: 200px"><story/></div>', | ||
}), | ||
], | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,95 @@ | ||
<template> | ||
<div class="relative flex items-start justify-between"> | ||
<Menu | ||
as="div" | ||
:class=" | ||
position === 'right' | ||
? 'ltr:ml-auto rtl:mr-auto' | ||
: 'ltr:mr-auto rtl:ml-auto' | ||
" | ||
> | ||
<div> | ||
<MenuButton | ||
class="bg-white hover:bg-gray-50 inline-flex w-full justify-center gap-x-1.5 rounded px-3 py-2" | ||
:class="[ | ||
name ? 'border border-light text-lg-normal' : 'text-3xl-normal', | ||
]" | ||
> | ||
{{ name }} | ||
<Icon | ||
class="-mr-1 h-5 w-5 text-primary" | ||
:icon="name ? 'Dropdown' : 'MoreOptions'" | ||
aria-hidden="true" | ||
/> | ||
</MenuButton> | ||
</div> | ||
|
||
<transition | ||
enter-active-class="transition ease-out duration-100" | ||
enter-from-class="transform opacity-0 scale-95" | ||
enter-to-class="transform opacity-100 scale-100" | ||
leave-active-class="transition ease-in duration-75" | ||
leave-from-class="transform opacity-100 scale-100" | ||
leave-to-class="transform opacity-0 scale-95" | ||
> | ||
<MenuItems | ||
class="bg-white dropdown-shadow absolute z-10 w-max border border-light focus:outline-none" | ||
:class=" | ||
position === 'right' | ||
? 'ltr:right-0 ltr:origin-top-right rtl:left-0 rtl:origin-top-right' | ||
: 'ltr:left-0 ltr:origin-top-left rtl:right-0 rtl:origin-top-left' | ||
" | ||
> | ||
<MenuItem v-for="(action, i) in actions" :key="i" v-slot="{active}"> | ||
<div class="w-auto"> | ||
<PkpButton | ||
v-if="action.label" | ||
element="a" | ||
:href="action.url" | ||
:icon="action.icon" | ||
:is-active="active" | ||
:is-warnable="action.isWarnable" | ||
:class="i !== actions.length - 1 ? 'border-b' : ''" | ||
size-variant="fullWidth" | ||
class="border-light" | ||
> | ||
{{ action.label }} | ||
</PkpButton> | ||
</div> | ||
</MenuItem> | ||
</MenuItems> | ||
</transition> | ||
</Menu> | ||
</div> | ||
</template> | ||
|
||
<script setup> | ||
import {Menu, MenuButton, MenuItem, MenuItems} from '@headlessui/vue'; | ||
defineProps({ | ||
actions: { | ||
type: Array, | ||
required: true, | ||
validator: (actions) => { | ||
return actions.every( | ||
(action) => | ||
typeof action.label === 'string' && action.label.trim() !== '', | ||
); | ||
}, | ||
}, | ||
name: { | ||
type: String, | ||
default: undefined, | ||
}, | ||
position: { | ||
type: String, | ||
default: 'right', | ||
}, | ||
}); | ||
</script> | ||
|
||
<style scoped> | ||
.dropdown-shadow { | ||
box-shadow: 0px 4px 10px 1px rgba(0, 0, 0, 0.5); | ||
} | ||
</style> |