Skip to content

Commit

Permalink
feat(action-menu): add <SActionMenu> (#426)
Browse files Browse the repository at this point in the history
Co-authored-by: Divyansh Singh <40380293+brc-dd@users.noreply.github.com>
  • Loading branch information
kiaking and brc-dd committed Dec 27, 2023
1 parent 78863e9 commit a6a7baf
Show file tree
Hide file tree
Showing 14 changed files with 718 additions and 538 deletions.
1 change: 1 addition & 0 deletions lib/components/SActionList.vue
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ defineProps<{
v-for="item, index in list"
:key="index"
:lead-icon="item.leadIcon"
:label="item.label"
:text="item.text"
:link="item.link"
:on-click="item.onClick"
Expand Down
15 changes: 12 additions & 3 deletions lib/components/SActionListItem.vue
Original file line number Diff line number Diff line change
@@ -1,16 +1,25 @@
<script setup lang="ts">
import { type IconifyIcon } from '@iconify/vue/dist/offline'
import { computed } from 'vue'
import SIcon from './SIcon.vue'
import SLink from './SLink.vue'
export interface ActionListItem {
leadIcon?: IconifyIcon
text: string
link?: string
label?: string
disabled?: boolean
onClick?(): void
/** @deprecated Use `:label` instead. */
text?: string
}
defineProps<ActionListItem>()
const props = defineProps<ActionListItem>()
const _label = computed(() => {
return props.label ?? props.text
})
</script>

<template>
Expand All @@ -23,7 +32,7 @@ defineProps<ActionListItem>()
<span v-if="leadIcon" class="lead-icon">
<SIcon class="lead-icon-svg" :icon="leadIcon" />
</span>
<span class="text">{{ text }}</span>
<span class="text">{{ _label }}</span>
</component>
</template>
Expand Down
84 changes: 84 additions & 0 deletions lib/components/SActionMenu.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
<script setup lang="ts">
import { ref } from 'vue'
import { type DropdownSection, useManualDropdownPosition } from '../composables/Dropdown'
import { useFlyout } from '../composables/Flyout'
import SButton, { type Mode, type Size, type Tooltip, type Type } from './SButton.vue'
import SDropdown from './SDropdown.vue'
export type { Mode, Size, Tooltip, Type }
const props = defineProps<{
tag?: string
size?: Size
type?: Type
mode?: Mode
icon?: any
iconMode?: Mode
label?: string
labelMode?: Mode
rounded?: boolean
block?: boolean
loading?: boolean
disabled?: boolean
tooltip?: Tooltip
options: DropdownSection[]
}>()
const container = ref<any>(null)
const { isOpen, toggle } = useFlyout(container)
const { position, update: updatePosition } = useManualDropdownPosition(container)
async function onOpen() {
if (!props.disabled) {
updatePosition()
toggle()
}
}
</script>

<template>
<div class="SActionMenu" :class="[block]" ref="container">
<div class="button">
<SButton
:tag="tag"
:size="size"
:type="type"
:mode="mode"
:icon="icon"
:icon-mode="iconMode"
:label="label"
:label-mode="labelMode"
:rounded="rounded"
:block="block"
:loading="loading"
:disabled="disabled"
:tooltip="tooltip"
@click="onOpen"
/>
</div>
<div v-if="isOpen" class="dropdown" :class="position">
<SDropdown :sections="options" />
</div>
</div>
</template>

<style scoped lang="postcss">
.SActionMenu {
position: relative;
display: inline-block;
}
.dropdown {
position: absolute;
left: 0;
z-index: var(--z-index-dropdown);
&.top { bottom: calc(100% + 8px); }
&.bottom { top: calc(100% + 8px); }
}
.SActionMenu.block {
display: block;
}
</style>
43 changes: 43 additions & 0 deletions lib/components/SControlActionMenu.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
<script setup lang="ts">
import { useControlSize } from '../composables/Control'
import { type DropdownSection } from '../composables/Dropdown'
import SActionMenu, { type Mode, type Tooltip, type Type } from './SActionMenu.vue'
defineProps<{
tag?: string
type?: Type
mode?: Mode
icon?: any
iconMode?: Mode
label?: string
labelMode?: Mode
href?: string
loading?: boolean
disabled?: boolean
tooltip?: Tooltip
options: DropdownSection[]
}>()
const size = useControlSize()
</script>

<template>
<div class="SControlActionMenu">
<SActionMenu
:tag="tag"
:size="size"
:type="type"
:mode="mode"
:icon="icon"
:icon-mode="iconMode"
:label="label"
:label-mode="labelMode"
:href="href"
:tooltip="tooltip"
:options="options"
block
:loading="loading"
:disabled="disabled"
/>
</div>
</template>
2 changes: 1 addition & 1 deletion lib/components/SDropdown.vue
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ defineProps<{
.SDropdown {
border: 1px solid var(--c-divider);
border-radius: 6px;
min-width: 256px;
min-width: 288px;
max-height: 384px;
overflow-y: auto;
white-space: normal;
Expand Down
39 changes: 5 additions & 34 deletions lib/components/SDropdownSectionMenu.vue
Original file line number Diff line number Diff line change
@@ -1,49 +1,20 @@
<script setup lang="ts">
import { type DropdownSectionMenuOption } from '../composables/Dropdown'
import SActionList, { type ActionList } from './SActionList.vue'
defineProps<{
options: DropdownSectionMenuOption[]
options: ActionList
}>()
</script>

<template>
<ul class="SDropdownSectionMenu">
<li v-for="option in options" :key="option.label" class="item">
<button
class="button"
@click="option.onClick"
:disabled="option.disabled"
>
{{ option.label }}
</button>
</li>
</ul>
<div class="SDropdownSectionMenu">
<SActionList :list="options" />
</div>
</template>

<style scoped lang="postcss">
.SDropdownSectionMenu {
padding: 8px;
background-color: var(--c-bg-elv-3);
}
.button {
display: block;
border-radius: 6px;
padding: 0 8px;
width: 100%;
text-align: left;
line-height: 32px;
font-size: 14px;
font-weight: 400;
transition: color 0.25s, background-color 0.25s;
&:hover:not(:disabled) {
background-color: var(--c-bg-mute-1);
}
&:disabled {
color: var(--c-text-3);
cursor: not-allowed;
}
}
</style>
39 changes: 31 additions & 8 deletions lib/composables/Dropdown.ts
Original file line number Diff line number Diff line change
@@ -1,27 +1,26 @@
import { useElementBounding, useWindowSize } from '@vueuse/core'
import { type Component, type MaybeRef, type Ref, ref, unref } from 'vue'
import { type ActionList } from '../components/SActionList.vue'

export type DropdownSection =
| DropdownSectionMenu
| DropdownSectionFilter
| DropdownSectionComponent
| DropdownSectionActions

export type DropdownSectionType = 'menu' | 'filter' | 'actions' | 'component'
export type DropdownSectionType =
| 'menu'
| 'filter'
| 'actions'
| 'component'

export interface DropdownSectionBase {
type: DropdownSectionType
}

export interface DropdownSectionMenu extends DropdownSectionBase {
type: 'menu'
options: DropdownSectionMenuOption[]
}

export interface DropdownSectionMenuOption {
label: string
disabled?: boolean
onClick(): void
options: ActionList
}

export interface DropdownSectionFilter extends DropdownSectionBase {
Expand Down Expand Up @@ -87,6 +86,30 @@ export function createDropdown(section: DropdownSection[]): DropdownSection[] {
return section
}

export function createDropdownMenu(
section: Omit<DropdownSectionMenu, 'type'>
): DropdownSectionMenu {
return { type: 'menu', ...section }
}

export function createDropdownFilter(
section: Omit<DropdownSectionFilter, 'type'>
): DropdownSectionFilter {
return { type: 'filter', ...section }
}

export function createDropdownActions(
section: Omit<DropdownSectionActions, 'type'>
): DropdownSectionActions {
return { type: 'actions', ...section }
}

export function createDropdownComponent(
section: Omit<DropdownSectionComponent, 'type'>
): DropdownSectionComponent {
return { type: 'component', ...section }
}

export function useManualDropdownPosition(
container?: Ref<any>,
initPosition?: 'top' | 'bottom'
Expand Down
3 changes: 3 additions & 0 deletions lib/mixins/Control.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { type App } from 'vue'
import SControl from '../components/SControl.vue'
import SControlActionMenu from '../components/SControlActionMenu.vue'
import SControlButton from '../components/SControlButton.vue'
import SControlCenter from '../components/SControlCenter.vue'
import SControlInputSearch from '../components/SControlInputSearch.vue'
Expand All @@ -9,6 +10,7 @@ import SControlText from '../components/SControlText.vue'

export function mixin(app: App): void {
app.component('SControl', SControl)
app.component('SControlActionMenu', SControlActionMenu)
app.component('SControlButton', SControlButton)
app.component('SControlCenter', SControlCenter)
app.component('SControlInputSearch', SControlInputSearch)
Expand All @@ -20,6 +22,7 @@ export function mixin(app: App): void {
declare module 'vue' {
export interface GlobalComponents {
SControl: typeof SControl
SControlActionMenu: typeof SControlActionMenu
SControlButton: typeof SControlButton
SControlCenter: typeof SControlCenter
SControlInputSearch: typeof SControlInputSearch
Expand Down
20 changes: 10 additions & 10 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@
"postcss": "^8.4.32",
"postcss-nested": "^6.0.1",
"v-calendar": "^3.1.2",
"vue": "^3.3.11",
"vue": "^3.3.13",
"vue-router": "^4.2.5"
},
"dependencies": {
Expand All @@ -79,16 +79,16 @@
"@types/file-saver": "^2.0.7",
"@types/lodash-es": "^4.17.12",
"@types/markdown-it": "^13.0.7",
"@types/node": "^20.10.4",
"@types/qs": "^6.9.10",
"@types/node": "^20.10.5",
"@types/qs": "^6.9.11",
"@vitejs/plugin-vue": "^4.5.2",
"@vitest/coverage-v8": "^1.0.4",
"@vitest/coverage-v8": "^1.1.0",
"@vue/test-utils": "^2.4.3",
"@vuelidate/core": "^2.0.3",
"@vuelidate/validators": "^2.0.4",
"@vueuse/core": "^10.7.0",
"body-scroll-lock": "4.0.0-beta.0",
"eslint": "^8.55.0",
"eslint": "^8.56.0",
"fuse.js": "^7.0.0",
"happy-dom": "^12.10.3",
"histoire": "^0.17.6",
Expand All @@ -102,11 +102,11 @@
"release-it": "^17.0.1",
"typescript": "~5.3.3",
"v-calendar": "^3.1.2",
"vite": "^5.0.9",
"vitepress": "1.0.0-rc.31",
"vitest": "^1.0.4",
"vue": "^3.3.11",
"vite": "^5.0.10",
"vitepress": "1.0.0-rc.32",
"vitest": "^1.1.0",
"vue": "^3.3.13",
"vue-router": "^4.2.5",
"vue-tsc": "^1.8.25"
"vue-tsc": "^1.8.26"
}
}

0 comments on commit a6a7baf

Please sign in to comment.