Skip to content

Commit

Permalink
feat(Command): add new VCommand component (command palette/menu)
Browse files Browse the repository at this point in the history
  • Loading branch information
gravitano committed Jun 28, 2023
1 parent 6e61b8b commit 88707ad
Show file tree
Hide file tree
Showing 15 changed files with 388 additions and 0 deletions.
4 changes: 4 additions & 0 deletions packages/command/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# Change Log

All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
43 changes: 43 additions & 0 deletions packages/command/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# Morpheme Command

Morpheme Command Palette Component.

## Installation

npm

```
npm i @morpheme/command
```

yarn

```
yarn add @morpheme/command
```

pnpm

```
pnpm i @morpheme/command
```

## Usage

```vue
<script setup lang="ts">
import {VCommand} from '@morpheme/command';
</script>
<template>
<VCommand />
</template>
```

## Documentation

Check out storybook documentation [here](https://gits-ui.web.app/?path=/story/components-command--default).

## License

MIT
53 changes: 53 additions & 0 deletions packages/command/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
{
"name": "@morpheme/command",
"version": "1.0.0-beta.10",
"description": "Morpheme Command Menu Component",
"scripts": {
"build": "vite build && vue-tsc --emitDeclarationOnly && mv dist/src dist/types",
"prepublishOnly": "npm run build",
"test": "vitest"
},
"keywords": [
"morpheme",
"ui-component",
"command-palette",
"command-menu",
"command"
],
"author": "Warsono <[email protected]>",
"license": "MIT",
"dependencies": {
"@morpheme/icon": "^1.0.0-beta.10",
"@morpheme/list": "^1.0.0-beta.10",
"@morpheme/modal": "^1.0.0-beta.10",
"vue": "^3.3.4"
},
"devDependencies": {
"@morpheme/tailwind-config": "^1.0.0-beta.10",
"@morpheme/utils": "^1.0.0-beta.9",
"@vue/test-utils": "^2.0.0-rc.17",
"c8": "^7.11.3",
"tailwindcss": "^3.3.1",
"vee-validate": "^4.5.9",
"vite": "^4.3.8",
"vitest": "^0.12.4"
},
"main": "dist/command.umd.js",
"unpkg": "dist/command.iife.js",
"jsdelivr": "dist/command.iife.js",
"module": "./dist/command.mjs",
"types": "./dist/types/index.d.ts",
"exports": {
".": {
"import": "./dist/command.mjs",
"require": "./dist/command.js"
},
"./package.json": "./package.json",
"./src/*": "./src/*",
"./*": "./*"
},
"publishConfig": {
"access": "public"
},
"gitHead": "95edc430940e02006a6f7ccc5065333389c50ccd"
}
6 changes: 6 additions & 0 deletions packages/command/postcss.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
module.exports = {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
}
38 changes: 38 additions & 0 deletions packages/command/src/VCommand.stories.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import type {StoryFn, Meta} from '@storybook/vue3';
import VCommand from './VCommand.vue';
import {ref} from 'vue';

const items = [
{ value: 1, text: 'Wade Cooper', prependIcon: 'ri:line-fill' },
{ value: 2, text: 'Arlene Mccoy' },
{ value: 3, text: 'Devon Webb' },
{ value: 4, text: 'Tom Cook' },
{ value: 5, text: 'Tanya Fox' },
{ value: 6, text: 'Hellen Schmidt' },
]

export default {
title: 'Components/Command',
component: VCommand,
args: {
items
}
} as Meta;

export const Default: StoryFn = (args) => ({
setup() {
const selected = ref()
return {args, selected};
},
components: {
VCommand,
},
template: `
<VCommand
v-bind="args"
v-model="selected"
/>
Selected: {{selected}}
`,
});
10 changes: 10 additions & 0 deletions packages/command/src/VCommand.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { mount } from '@vue/test-utils';
import VCommand from './VCommand.vue';

describe('VCommand', () => {
it('renders the component with default props', () => {
const wrapper = mount(VCommand);

expect(wrapper.exists()).toBe(true);
});
});
140 changes: 140 additions & 0 deletions packages/command/src/VCommand.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
<script setup lang="ts">
import VModal from '@morpheme/modal';
import {computed, onMounted, onUnmounted, ref, watch} from 'vue';
import {
Combobox,
ComboboxInput,
ComboboxOptions,
ComboboxOption,
} from '@headlessui/vue';
import VIcon from '@morpheme/icon';
import {List as VList, ListItem as VListItem} from '@morpheme/list';
interface Props {
modelValue?: Record<string, any>;
items?: Record<string, any>[];
placeholder?: string;
icon?: string;
iconSize?: string;
iconClass?: string;
searchBy?: string;
emptyText?: string;
}
const props = withDefaults(defineProps<Props>(), {
items: () => [],
placeholder: 'Search...',
icon: 'ri:search-line',
searchBy: 'text',
emptyText: 'No results found',
});
const emit =
defineEmits<{
(e: 'update:modelValue', value: Record<string, any> | undefined): void;
}>();
const isOpen = ref(false);
const selected = ref(props.modelValue);
const query = ref('');
const filteredItems = computed(() =>
query.value === ''
? props.items
: props.items.filter((item) =>
item[props.searchBy]
.toLowerCase()
.replace(/\s+/g, '')
.includes(query.value.toLowerCase().replace(/\s+/g, '')),
),
);
watch(
() => props.modelValue,
(val) => {
selected.value = val;
},
);
watch(selected, (val) => {
emit('update:modelValue', val);
isOpen.value = false;
});
function onKeydown(event: KeyboardEvent) {
if (event.key === 'k' && (event.metaKey || event.ctrlKey)) {
event.preventDefault();
isOpen.value = !isOpen.value;
}
}
onMounted(() => {
window.addEventListener('keydown', onKeydown);
});
onUnmounted(() => {
window.removeEventListener('keydown', onKeydown);
});
</script>

<template>
<VModal v-model="isOpen" hide-footer hide-header body-class="v-command-body">
<Combobox v-model="selected">
<div class="v-input">
<div class="v-input-wrapper">
<div class="v-input-prepend">
<slot name="icon">
<VIcon :name="icon" :size="iconSize" :class="iconClass" />
</slot>
</div>
<ComboboxInput
@change="query = $event.target.value"
class="v-input-control"
:placeholder="placeholder"
/>
</div>
</div>
<ComboboxOptions class="v-command-options">
<VList flush>
<slot
name="empty"
v-if="filteredItems.length === 0"
:items="filteredItems"
>
<VListItem> {{ emptyText }} </VListItem>
</slot>
<slot name="prepend" />
<slot v-bind="{items: filteredItems}">
<ComboboxOption
v-for="item in filteredItems"
:key="item.id"
:value="item"
v-slot="{active, selected, disabled}"
>
<slot name="item" v-bind="{active, selected, item, disabled}">
<VListItem
v-bind="item"
:class="{
'v-list-item--hover': active,
}"
>
{{ item.text }}
</VListItem>
</slot>
</ComboboxOption>
</slot>
<slot name="append" />
</VList>
</ComboboxOptions>
</Combobox>
</VModal>
</template>

<style>
.v-command-body {
margin-top: 0;
}
.v-command-options {
margin-top: 1rem;
}
</style>
2 changes: 2 additions & 0 deletions packages/command/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export {default as VCommand} from './VCommand.vue';
export {default} from './VCommand.vue';
6 changes: 6 additions & 0 deletions packages/command/src/stories/Changelog.stories.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import {Meta, Description} from '@storybook/addon-docs';
import Changelog from '../../CHANGELOG.md';

<Meta title="Components/Command/Changelog" />

<Changelog />
6 changes: 6 additions & 0 deletions packages/command/src/stories/Readme.stories.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import {Meta, Description} from '@storybook/addon-docs';
import Readme from '../../README.md';

<Meta title="Components/Command/Readme" />

<Readme />
5 changes: 5 additions & 0 deletions packages/command/src/vue.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
declare module '*.vue' {
import type {DefineComponent} from 'vue';
const component: DefineComponent<{}, {}, any>;
export default component;
}
4 changes: 4 additions & 0 deletions packages/command/tailwind.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
module.exports = {
content: ['./src/**/*.{vue,js,ts,jsx,tsx}'],
presets: [require('@morpheme/tailwind-config/preset')],
};
22 changes: 22 additions & 0 deletions packages/command/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
{
"compilerOptions": {
"baseUrl": ".",
"outDir": "dist",
"declaration": true,
"sourceMap": false,
"target": "esnext",
"module": "esnext",
"moduleResolution": "node",
"allowJs": true,
"strict": true,
"noUnusedLocals": true,
"rootDir": ".",
"skipLibCheck": true,
"types": ["vite/client", "vitest/globals"],
"emitDeclarationOnly": true,
"allowSyntheticDefaultImports": true,
"jsx": "preserve"
},
"include": ["src/vue.d.ts", "*.vue", "src"],
"exclude": ["**/*.stories.ts", "**/*.spec.ts", "**/*.test.ts"]
}
Loading

0 comments on commit 88707ad

Please sign in to comment.