pimelon-ui 0.1.0-alpha.9 → 0.1.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +7 -13
- package/src/components/Autocomplete.story.vue +25 -0
- package/src/components/Autocomplete.vue +38 -22
- package/src/components/Avatar.story.vue +28 -0
- package/src/components/Badge.story.vue +26 -0
- package/src/components/Badge.vue +6 -2
- package/src/components/Breadcrumbs.story.vue +73 -0
- package/src/components/Breadcrumbs.vue +108 -0
- package/src/components/Button.story.vue +31 -0
- package/src/components/Checkbox.story.vue +22 -0
- package/src/components/Checkbox.vue +4 -1
- package/src/components/CommandPalette/CommandPalette.vue +110 -0
- package/src/components/CommandPalette/CommandPaletteItem.vue +28 -0
- package/src/components/Dialog.story.vue +39 -0
- package/src/components/Dialog.vue +1 -1
- package/src/components/Dropdown.story.vue +98 -0
- package/src/components/ErrorMessage.story.vue +28 -0
- package/src/components/FileUploader.story.vue +34 -0
- package/src/components/FormControl.story.vue +101 -0
- package/src/components/ListFilter/FilterIcon.vue +31 -0
- package/src/components/ListFilter/ListFilter.vue +292 -0
- package/src/components/ListFilter/NestedPopover.vue +60 -0
- package/src/components/ListFilter/SearchComplete.vue +85 -0
- package/src/components/ListView/ListHeader.vue +34 -0
- package/src/components/ListView/ListHeaderItem.vue +22 -0
- package/src/components/ListView/ListRow.vue +72 -0
- package/src/components/ListView/ListRowItem.vue +45 -0
- package/src/components/ListView/ListRows.vue +14 -0
- package/src/components/ListView/ListSelectBanner.vue +86 -0
- package/src/components/ListView/ListView.vue +96 -0
- package/src/components/ListView/utils.js +22 -0
- package/src/components/ListView.story.md +110 -0
- package/src/components/ListView.story.vue +208 -0
- package/src/components/Popover.story.vue +29 -0
- package/src/components/Progress.story.vue +34 -0
- package/src/components/Select.story.vue +21 -0
- package/src/components/Select.vue +1 -0
- package/src/components/Spinner.story.vue +14 -0
- package/src/components/Switch.story.vue +28 -0
- package/src/components/Switch.vue +11 -15
- package/src/components/TabButtons.vue +54 -0
- package/src/components/Tabs.story.vue +72 -0
- package/src/components/Tabs.vue +97 -0
- package/src/components/TextInput.story.vue +81 -0
- package/src/components/TextInput.vue +8 -2
- package/src/components/Textarea.vue +1 -0
- package/src/components/Tooltip.story.vue +11 -0
- package/src/env.d.ts +2 -0
- package/src/index.js +13 -0
- package/src/resources/documentResource.js +45 -5
- package/src/resources/listResource.js +16 -5
- package/src/style.css +6 -0
- package/src/utils/fileUploadHandler.ts +62 -57
- package/src/utils/{melonRequest.js → frappeRequest.js} +2 -9
- package/src/components/Avatar.stories.ts +0 -110
- package/src/components/Badge.stories.js +0 -149
- package/src/components/Button.stories.js +0 -173
- package/src/components/Divider.stories.ts +0 -110
- package/src/components/Dropdown.stories.ts +0 -73
- package/src/components/Progress.stories.js +0 -80
- package/src/components/Spinner.stories.ts +0 -13
- package/src/components/Switch.stories.js +0 -52
- package/src/components/TextInput.stories.ts +0 -143
- package/src/tokens/Color.vue +0 -194
- package/src/utils/tailwind.config.stories.js +0 -8
- package/src/vite-env.d.ts +0 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pimelon-ui",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.13",
|
|
4
4
|
"description": "A set of components and utilities for rapid UI development",
|
|
5
5
|
"main": "./src/index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -14,8 +14,9 @@
|
|
|
14
14
|
"dev": "vite",
|
|
15
15
|
"build": "vite build",
|
|
16
16
|
"preview": "vite preview",
|
|
17
|
-
"
|
|
18
|
-
"build
|
|
17
|
+
"story:dev": "histoire dev",
|
|
18
|
+
"story:build": "histoire build",
|
|
19
|
+
"story:preview": "histoire preview"
|
|
19
20
|
},
|
|
20
21
|
"files": [
|
|
21
22
|
"src",
|
|
@@ -49,6 +50,7 @@
|
|
|
49
50
|
"@tiptap/starter-kit": "^2.0.3",
|
|
50
51
|
"@tiptap/suggestion": "^2.0.3",
|
|
51
52
|
"@tiptap/vue-3": "^2.0.3",
|
|
53
|
+
"@vueuse/core": "^10.4.1",
|
|
52
54
|
"feather-icons": "^4.28.0",
|
|
53
55
|
"idb-keyval": "^6.2.0",
|
|
54
56
|
"showdown": "^2.1.0",
|
|
@@ -60,24 +62,16 @@
|
|
|
60
62
|
"vue-router": "^4.1.6"
|
|
61
63
|
},
|
|
62
64
|
"devDependencies": {
|
|
63
|
-
"@
|
|
64
|
-
"@storybook/addon-interactions": "^7.0.0-beta.61",
|
|
65
|
-
"@storybook/addon-links": "^7.0.0-beta.61",
|
|
66
|
-
"@storybook/blocks": "^7.0.0-alpha.8",
|
|
67
|
-
"@storybook/testing-library": "^0.0.14-next.1",
|
|
68
|
-
"@storybook/vue3": "^7.0.0-beta.61",
|
|
69
|
-
"@storybook/vue3-vite": "^7.0.0-beta.61",
|
|
65
|
+
"@histoire/plugin-vue": "^0.16.1",
|
|
70
66
|
"@vitejs/plugin-vue": "^4.0.0",
|
|
71
67
|
"autoprefixer": "^10.4.13",
|
|
72
68
|
"cross-fetch": "^3.1.5",
|
|
69
|
+
"histoire": "^0.16.2",
|
|
73
70
|
"husky": "^8.0.3",
|
|
74
71
|
"lint-staged": ">=10",
|
|
75
72
|
"postcss": "^8.4.21",
|
|
76
73
|
"prettier": "2.7.1",
|
|
77
74
|
"prettier-plugin-tailwindcss": "^0.1.13",
|
|
78
|
-
"react": "^18.2.0",
|
|
79
|
-
"react-dom": "^18.2.0",
|
|
80
|
-
"storybook": "^7.0.0-beta.61",
|
|
81
75
|
"tailwindcss": "^3.2.7",
|
|
82
76
|
"typescript": "^5.0.2",
|
|
83
77
|
"vite": "^4.1.0",
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { ref } from 'vue'
|
|
3
|
+
import Autocomplete from './Autocomplete.vue'
|
|
4
|
+
|
|
5
|
+
const value = ref('')
|
|
6
|
+
const options = [
|
|
7
|
+
{ label: 'John Doe', value: 'john-doe' },
|
|
8
|
+
{ label: 'Jane Doe', value: 'jane-doe' },
|
|
9
|
+
{ label: 'John Smith', value: 'john-smith' },
|
|
10
|
+
{ label: 'Jane Smith', value: 'jane-smith' },
|
|
11
|
+
{ label: 'John Wayne', value: 'john-wayne' },
|
|
12
|
+
{ label: 'Jane Wayne', value: 'jane-wayne' },
|
|
13
|
+
]
|
|
14
|
+
</script>
|
|
15
|
+
<template>
|
|
16
|
+
<Story :layout="{ width: 500, type: 'grid' }" autoPropsDisabled>
|
|
17
|
+
<div class="p-2">
|
|
18
|
+
<Autocomplete
|
|
19
|
+
:options="options"
|
|
20
|
+
v-model="value"
|
|
21
|
+
placeholder="Select person"
|
|
22
|
+
/>
|
|
23
|
+
</div>
|
|
24
|
+
</Story>
|
|
25
|
+
</template>
|
|
@@ -2,28 +2,33 @@
|
|
|
2
2
|
<Combobox v-model="selectedValue" nullable v-slot="{ open: isComboboxOpen }">
|
|
3
3
|
<Popover class="w-full" v-model:show="showOptions">
|
|
4
4
|
<template #target="{ open: openPopover, togglePopover }">
|
|
5
|
-
<
|
|
6
|
-
<
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
<span
|
|
12
|
-
class="overflow-hidden text-ellipsis whitespace-nowrap text-base leading-5"
|
|
13
|
-
v-if="selectedValue"
|
|
5
|
+
<slot name="target" v-bind="{ open: openPopover, togglePopover }">
|
|
6
|
+
<div class="w-full">
|
|
7
|
+
<button
|
|
8
|
+
class="flex h-7 w-full items-center justify-between gap-2 rounded bg-gray-100 px-2 py-1 transition-colors hover:bg-gray-200 focus:ring-2 focus:ring-gray-400"
|
|
9
|
+
:class="{ 'bg-gray-200': isComboboxOpen }"
|
|
10
|
+
@click="() => togglePopover()"
|
|
14
11
|
>
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
12
|
+
<div class="flex items-center">
|
|
13
|
+
<slot name="prefix" />
|
|
14
|
+
<span
|
|
15
|
+
class="overflow-hidden text-ellipsis whitespace-nowrap text-base leading-5"
|
|
16
|
+
v-if="selectedValue"
|
|
17
|
+
>
|
|
18
|
+
{{ displayValue(selectedValue) }}
|
|
19
|
+
</span>
|
|
20
|
+
<span class="text-base leading-5 text-gray-500" v-else>
|
|
21
|
+
{{ placeholder || '' }}
|
|
22
|
+
</span>
|
|
23
|
+
</div>
|
|
24
|
+
<FeatherIcon
|
|
25
|
+
name="chevron-down"
|
|
26
|
+
class="h-4 w-4 text-gray-600"
|
|
27
|
+
aria-hidden="true"
|
|
28
|
+
/>
|
|
29
|
+
</button>
|
|
30
|
+
</div>
|
|
31
|
+
</slot>
|
|
27
32
|
</template>
|
|
28
33
|
<template #body="{ isOpen }">
|
|
29
34
|
<div v-show="isOpen">
|
|
@@ -36,6 +41,7 @@
|
|
|
36
41
|
>
|
|
37
42
|
<div class="relative w-full">
|
|
38
43
|
<ComboboxInput
|
|
44
|
+
ref="search"
|
|
39
45
|
class="form-input w-full"
|
|
40
46
|
type="text"
|
|
41
47
|
@change="
|
|
@@ -80,7 +86,10 @@
|
|
|
80
86
|
{ 'bg-gray-100': active },
|
|
81
87
|
]"
|
|
82
88
|
>
|
|
83
|
-
<slot
|
|
89
|
+
<slot
|
|
90
|
+
name="item-prefix"
|
|
91
|
+
v-bind="{ active, selected, option }"
|
|
92
|
+
/>
|
|
84
93
|
{{ option.label }}
|
|
85
94
|
</li>
|
|
86
95
|
</ComboboxOption>
|
|
@@ -168,6 +177,13 @@ export default {
|
|
|
168
177
|
query(q) {
|
|
169
178
|
this.$emit('update:query', q)
|
|
170
179
|
},
|
|
180
|
+
showOptions(val) {
|
|
181
|
+
if (val) {
|
|
182
|
+
this.$nextTick(() => {
|
|
183
|
+
this.$refs.search.el.focus()
|
|
184
|
+
})
|
|
185
|
+
}
|
|
186
|
+
},
|
|
171
187
|
},
|
|
172
188
|
methods: {
|
|
173
189
|
filterOptions(options) {
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { reactive } from 'vue'
|
|
3
|
+
import Avatar from './Avatar.vue'
|
|
4
|
+
const state = reactive({
|
|
5
|
+
image: 'https://avatars.githubusercontent.com/u/499550?s=60&v=4',
|
|
6
|
+
label: 'EY',
|
|
7
|
+
size: 'md',
|
|
8
|
+
})
|
|
9
|
+
|
|
10
|
+
const shapes = ['circle', 'square']
|
|
11
|
+
const sizes = ['xs', 'sm', 'md', 'lg', 'xl', '2xl', '3xl']
|
|
12
|
+
</script>
|
|
13
|
+
|
|
14
|
+
<template>
|
|
15
|
+
<Story :layout="{ type: 'grid', width: 300 }">
|
|
16
|
+
<Variant v-for="shape in shapes" :key="shape" :title="shape">
|
|
17
|
+
<Avatar :shape="shape" v-bind="state" />
|
|
18
|
+
</Variant>
|
|
19
|
+
<Variant v-for="shape in shapes" :key="shape" :title="shape">
|
|
20
|
+
<Avatar :shape="shape" v-bind="state" :image="null" />
|
|
21
|
+
</Variant>
|
|
22
|
+
|
|
23
|
+
<template #controls>
|
|
24
|
+
<HstText v-model="state.label" title="Label" />
|
|
25
|
+
<HstSelect v-model="state.size" :options="sizes" title="Size" />
|
|
26
|
+
</template>
|
|
27
|
+
</Story>
|
|
28
|
+
</template>
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { reactive } from 'vue'
|
|
3
|
+
import Badge from './Badge.vue'
|
|
4
|
+
const state = reactive({
|
|
5
|
+
theme: 'gray',
|
|
6
|
+
size: 'sm',
|
|
7
|
+
label: 'Badge',
|
|
8
|
+
})
|
|
9
|
+
const variants = ['solid', 'subtle', 'outline', 'ghost']
|
|
10
|
+
const themes = ['gray', 'blue', 'green', 'orange', 'red']
|
|
11
|
+
const sizes = ['sm', 'md', 'lg']
|
|
12
|
+
</script>
|
|
13
|
+
|
|
14
|
+
<template>
|
|
15
|
+
<Story :layout="{ type: 'grid', width: 300 }">
|
|
16
|
+
<Variant v-for="variant in variants" :key="variant" :title="variant">
|
|
17
|
+
<Badge :variant="variant" v-bind="state">{{ state.label }}</Badge>
|
|
18
|
+
</Variant>
|
|
19
|
+
|
|
20
|
+
<template #controls>
|
|
21
|
+
<HstText v-model="state.label" title="Content" />
|
|
22
|
+
<HstSelect v-model="state.theme" :options="themes" title="Theme" />
|
|
23
|
+
<HstSelect v-model="state.size" :options="sizes" title="Size" />
|
|
24
|
+
</template>
|
|
25
|
+
</Story>
|
|
26
|
+
</template>
|
package/src/components/Badge.vue
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
>
|
|
10
10
|
<slot name="prefix"></slot>
|
|
11
11
|
</div>
|
|
12
|
-
<slot>{{ props.label }}</slot>
|
|
12
|
+
<slot>{{ props.label?.toString() }}</slot>
|
|
13
13
|
<div
|
|
14
14
|
:class="[props.size == 'lg' ? 'max-h-6' : 'max-h-4']"
|
|
15
15
|
v-if="$slots.suffix"
|
|
@@ -22,11 +22,15 @@
|
|
|
22
22
|
<script lang="ts" setup>
|
|
23
23
|
import { computed } from 'vue'
|
|
24
24
|
|
|
25
|
+
interface Label {
|
|
26
|
+
toString(): string
|
|
27
|
+
}
|
|
28
|
+
|
|
25
29
|
interface BadgeProps {
|
|
26
30
|
theme?: 'gray' | 'blue' | 'green' | 'orange' | 'red'
|
|
27
31
|
size?: 'sm' | 'md' | 'lg'
|
|
28
32
|
variant?: 'solid' | 'subtle' | 'outline' | 'ghost'
|
|
29
|
-
label?: string
|
|
33
|
+
label?: Label | string | number
|
|
30
34
|
}
|
|
31
35
|
|
|
32
36
|
const props = withDefaults(defineProps<BadgeProps>(), {
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { logEvent } from 'histoire/client'
|
|
3
|
+
import Breadcrumbs from './Breadcrumbs.vue'
|
|
4
|
+
</script>
|
|
5
|
+
|
|
6
|
+
<template>
|
|
7
|
+
<Story :layout="{ type: 'grid', width: 500 }">
|
|
8
|
+
<Variant title="With route option">
|
|
9
|
+
<Breadcrumbs
|
|
10
|
+
:items="[
|
|
11
|
+
{
|
|
12
|
+
label: 'Home',
|
|
13
|
+
route: { name: 'Home' },
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
label: 'Views',
|
|
17
|
+
route: '/components',
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
label: 'List',
|
|
21
|
+
route: '/components/breadcrumbs',
|
|
22
|
+
},
|
|
23
|
+
]"
|
|
24
|
+
/>
|
|
25
|
+
</Variant>
|
|
26
|
+
<Variant title="With onClick option">
|
|
27
|
+
<Breadcrumbs
|
|
28
|
+
:items="[
|
|
29
|
+
{
|
|
30
|
+
label: 'Home',
|
|
31
|
+
onClick: () => logEvent('onClick', 'Home'),
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
label: 'Views',
|
|
35
|
+
onClick: () => logEvent('onClick', 'Home'),
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
label: 'Kanban',
|
|
39
|
+
onClick: () => logEvent('onClick', 'Home'),
|
|
40
|
+
},
|
|
41
|
+
]"
|
|
42
|
+
/>
|
|
43
|
+
</Variant>
|
|
44
|
+
|
|
45
|
+
<Variant title="With prefix slot">
|
|
46
|
+
<Breadcrumbs
|
|
47
|
+
:items="[
|
|
48
|
+
{
|
|
49
|
+
label: 'Home',
|
|
50
|
+
icon: '🏡',
|
|
51
|
+
route: { name: 'Home' },
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
label: 'Views',
|
|
55
|
+
icon: '🏞️',
|
|
56
|
+
route: '/components',
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
label: 'List',
|
|
60
|
+
icon: '📃',
|
|
61
|
+
route: '/components/breadcrumbs',
|
|
62
|
+
},
|
|
63
|
+
]"
|
|
64
|
+
>
|
|
65
|
+
<template #prefix="{ item }">
|
|
66
|
+
<span class="mr-1">
|
|
67
|
+
{{ item.icon }}
|
|
68
|
+
</span>
|
|
69
|
+
</template>
|
|
70
|
+
</Breadcrumbs>
|
|
71
|
+
</Variant>
|
|
72
|
+
</Story>
|
|
73
|
+
</template>
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="flex min-w-0 items-center">
|
|
3
|
+
<template v-if="dropdownItems.length">
|
|
4
|
+
<Dropdown class="h-7" :options="dropdownItems">
|
|
5
|
+
<Button variant="ghost">
|
|
6
|
+
<template #icon>
|
|
7
|
+
<svg
|
|
8
|
+
class="w-4 text-gray-600"
|
|
9
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
10
|
+
width="24"
|
|
11
|
+
height="24"
|
|
12
|
+
viewBox="0 0 24 24"
|
|
13
|
+
fill="none"
|
|
14
|
+
stroke="currentColor"
|
|
15
|
+
stroke-width="2"
|
|
16
|
+
stroke-linecap="round"
|
|
17
|
+
stroke-linejoin="round"
|
|
18
|
+
>
|
|
19
|
+
<circle cx="12" cy="12" r="1" />
|
|
20
|
+
<circle cx="19" cy="12" r="1" />
|
|
21
|
+
<circle cx="5" cy="12" r="1" />
|
|
22
|
+
</svg>
|
|
23
|
+
</template>
|
|
24
|
+
</Button>
|
|
25
|
+
</Dropdown>
|
|
26
|
+
<span class="ml-1 mr-0.5 text-base text-gray-500" aria-hidden="true">
|
|
27
|
+
/
|
|
28
|
+
</span>
|
|
29
|
+
</template>
|
|
30
|
+
<div
|
|
31
|
+
class="flex min-w-0 items-center overflow-hidden text-ellipsis whitespace-nowrap"
|
|
32
|
+
>
|
|
33
|
+
<template v-for="(item, i) in crumbs" :key="item.label">
|
|
34
|
+
<component
|
|
35
|
+
:is="item.route ? 'router-link' : 'button'"
|
|
36
|
+
class="flex items-center rounded px-0.5 py-1 text-lg font-medium focus:outline-none focus-visible:ring-2 focus-visible:ring-gray-400"
|
|
37
|
+
:class="[
|
|
38
|
+
i == crumbs.length - 1
|
|
39
|
+
? 'text-gray-900'
|
|
40
|
+
: 'text-gray-600 hover:text-gray-700',
|
|
41
|
+
]"
|
|
42
|
+
v-bind="item.route ? { to: item.route } : { onClick: item.onClick }"
|
|
43
|
+
>
|
|
44
|
+
<slot name="prefix" :item="item" />
|
|
45
|
+
<span>
|
|
46
|
+
{{ item.label }}
|
|
47
|
+
</span>
|
|
48
|
+
</component>
|
|
49
|
+
<span
|
|
50
|
+
v-if="i != crumbs.length - 1"
|
|
51
|
+
class="mx-0.5 text-base text-gray-500"
|
|
52
|
+
aria-hidden="true"
|
|
53
|
+
>
|
|
54
|
+
/
|
|
55
|
+
</span>
|
|
56
|
+
</template>
|
|
57
|
+
</div>
|
|
58
|
+
</div>
|
|
59
|
+
</template>
|
|
60
|
+
<script setup lang="ts">
|
|
61
|
+
import { useWindowSize } from '@vueuse/core'
|
|
62
|
+
import { computed } from 'vue'
|
|
63
|
+
import { RouterLinkProps, useRouter } from 'vue-router'
|
|
64
|
+
import Dropdown from '../components/Dropdown.vue'
|
|
65
|
+
import Button from '../components/Button.vue'
|
|
66
|
+
|
|
67
|
+
interface BreadcrumbItem {
|
|
68
|
+
label: string
|
|
69
|
+
route?: RouterLinkProps['to']
|
|
70
|
+
onClick?: () => void
|
|
71
|
+
[key: string]: any
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
interface BreadcrumbsProps {
|
|
75
|
+
items: BreadcrumbItem[]
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const props = defineProps<BreadcrumbsProps>()
|
|
79
|
+
|
|
80
|
+
const router = useRouter()
|
|
81
|
+
const { width } = useWindowSize()
|
|
82
|
+
|
|
83
|
+
const items = computed(() => {
|
|
84
|
+
return (props.items || []).filter(Boolean)
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
const dropdownItems = computed(() => {
|
|
88
|
+
if (width.value > 640) return []
|
|
89
|
+
|
|
90
|
+
let allExceptLastTwo = items.value.slice(0, -2)
|
|
91
|
+
return allExceptLastTwo.map((item) => {
|
|
92
|
+
let onClick = item.onClick ? item.onClick : () => router.push(item.route)
|
|
93
|
+
return {
|
|
94
|
+
...item,
|
|
95
|
+
icon: null,
|
|
96
|
+
label: item.label,
|
|
97
|
+
onClick,
|
|
98
|
+
}
|
|
99
|
+
})
|
|
100
|
+
})
|
|
101
|
+
|
|
102
|
+
const crumbs = computed(() => {
|
|
103
|
+
if (width.value > 640) return items.value
|
|
104
|
+
|
|
105
|
+
let lastTwo = items.value.slice(-2)
|
|
106
|
+
return lastTwo
|
|
107
|
+
})
|
|
108
|
+
</script>
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { reactive } from 'vue'
|
|
3
|
+
import Button from './Button.vue'
|
|
4
|
+
const state = reactive({
|
|
5
|
+
theme: 'gray',
|
|
6
|
+
size: 'sm',
|
|
7
|
+
label: 'Button',
|
|
8
|
+
loading: false,
|
|
9
|
+
loadingText: null,
|
|
10
|
+
disabled: false,
|
|
11
|
+
link: null,
|
|
12
|
+
})
|
|
13
|
+
const variants = ['solid', 'subtle', 'outline', 'ghost']
|
|
14
|
+
const themes = ['gray', 'blue', 'green', 'red']
|
|
15
|
+
const sizes = ['sm', 'md', 'lg', 'xl', '2xl']
|
|
16
|
+
</script>
|
|
17
|
+
|
|
18
|
+
<template>
|
|
19
|
+
<Story :layout="{ type: 'grid', width: 300 }">
|
|
20
|
+
<Variant v-for="variant in variants" :key="variant" :title="variant">
|
|
21
|
+
<Button :variant="variant" v-bind="state">{{ state.label }}</Button>
|
|
22
|
+
</Variant>
|
|
23
|
+
|
|
24
|
+
<template #controls>
|
|
25
|
+
<HstText v-model="state.label" title="Content" />
|
|
26
|
+
<HstCheckbox v-model="state.disabled" title="Disabled" />
|
|
27
|
+
<HstSelect v-model="state.theme" :options="themes" title="Theme" />
|
|
28
|
+
<HstSelect v-model="state.size" :options="sizes" title="Size" />
|
|
29
|
+
</template>
|
|
30
|
+
</Story>
|
|
31
|
+
</template>
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { reactive } from 'vue'
|
|
3
|
+
import Checkbox from './Checkbox.vue'
|
|
4
|
+
|
|
5
|
+
const state = reactive({
|
|
6
|
+
size: 'sm',
|
|
7
|
+
value: false,
|
|
8
|
+
})
|
|
9
|
+
|
|
10
|
+
const sizes = ['sm', 'md']
|
|
11
|
+
</script>
|
|
12
|
+
<template>
|
|
13
|
+
<Story :layout="{ width: 500, type: 'grid' }">
|
|
14
|
+
<div class="p-2">
|
|
15
|
+
<Checkbox v-bind="state" v-model="state.value" label="Enable feature" />
|
|
16
|
+
</div>
|
|
17
|
+
|
|
18
|
+
<template #controls>
|
|
19
|
+
<HstSelect v-model="state.size" :options="sizes" title="Size" />
|
|
20
|
+
</template>
|
|
21
|
+
</Story>
|
|
22
|
+
</template>
|
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
:id="htmlId"
|
|
17
17
|
:checked="Boolean(modelValue)"
|
|
18
18
|
@change="(e) => $emit('update:modelValue', (e.target as HTMLInputElement).checked)"
|
|
19
|
+
v-bind="attrs"
|
|
19
20
|
/>
|
|
20
21
|
<label class="block" :class="labelClasses" v-if="label" :for="htmlId">
|
|
21
22
|
{{ label }}
|
|
@@ -23,7 +24,7 @@
|
|
|
23
24
|
</div>
|
|
24
25
|
</template>
|
|
25
26
|
<script lang="ts" setup>
|
|
26
|
-
import { computed } from 'vue'
|
|
27
|
+
import { computed, useAttrs } from 'vue'
|
|
27
28
|
import { useId } from '../utils/useId'
|
|
28
29
|
|
|
29
30
|
interface CheckboxProps {
|
|
@@ -40,6 +41,8 @@ const props = withDefaults(defineProps<CheckboxProps>(), {
|
|
|
40
41
|
padding: false,
|
|
41
42
|
})
|
|
42
43
|
|
|
44
|
+
const attrs = useAttrs()
|
|
45
|
+
|
|
43
46
|
const htmlId = props.id ?? useId()
|
|
44
47
|
|
|
45
48
|
const labelClasses = computed(() => {
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<template>
|
|
3
|
+
<Dialog
|
|
4
|
+
v-model="show"
|
|
5
|
+
:options="{ size: 'xl', position: 'top' }"
|
|
6
|
+
@after-leave="searchQuery = ''"
|
|
7
|
+
>
|
|
8
|
+
<template #body>
|
|
9
|
+
<div>
|
|
10
|
+
<Combobox nullable @update:model-value="select">
|
|
11
|
+
<div class="relative">
|
|
12
|
+
<div class="absolute inset-y-0 left-0 flex items-center pl-4.5">
|
|
13
|
+
<FeatherIcon name="search" class="h-4 w-4" />
|
|
14
|
+
</div>
|
|
15
|
+
<ComboboxInput
|
|
16
|
+
placeholder="Search"
|
|
17
|
+
class="w-full border-none bg-transparent py-3 pl-11.5 pr-4.5 text-base text-gray-800 placeholder-gray-500 focus:ring-0"
|
|
18
|
+
v-model="searchQuery"
|
|
19
|
+
autocomplete="off"
|
|
20
|
+
/>
|
|
21
|
+
</div>
|
|
22
|
+
<ComboboxOptions
|
|
23
|
+
class="max-h-96 overflow-auto border-t border-gray-100"
|
|
24
|
+
static
|
|
25
|
+
:hold="true"
|
|
26
|
+
>
|
|
27
|
+
<div
|
|
28
|
+
class="mb-2 mt-4.5 first:mt-3"
|
|
29
|
+
v-for="(group, index) in groups"
|
|
30
|
+
:key="group.title"
|
|
31
|
+
>
|
|
32
|
+
<div
|
|
33
|
+
class="mb-2.5 px-4.5 text-base text-gray-600"
|
|
34
|
+
v-if="!group.hideTitle"
|
|
35
|
+
>
|
|
36
|
+
{{ group.title }}
|
|
37
|
+
</div>
|
|
38
|
+
<ComboboxOption
|
|
39
|
+
v-for="item in group.items"
|
|
40
|
+
:key="item.name"
|
|
41
|
+
v-slot="{ active }"
|
|
42
|
+
:value="item"
|
|
43
|
+
class="px-2.5"
|
|
44
|
+
:disabled="item.disabled"
|
|
45
|
+
>
|
|
46
|
+
<component
|
|
47
|
+
:is="group.component"
|
|
48
|
+
:item="item"
|
|
49
|
+
:active="active"
|
|
50
|
+
/>
|
|
51
|
+
</ComboboxOption>
|
|
52
|
+
</div>
|
|
53
|
+
</ComboboxOptions>
|
|
54
|
+
</Combobox>
|
|
55
|
+
</div>
|
|
56
|
+
</template>
|
|
57
|
+
</Dialog>
|
|
58
|
+
</template>
|
|
59
|
+
</template>
|
|
60
|
+
|
|
61
|
+
<script setup>
|
|
62
|
+
import {
|
|
63
|
+
Combobox,
|
|
64
|
+
ComboboxInput,
|
|
65
|
+
ComboboxOption,
|
|
66
|
+
ComboboxOptions,
|
|
67
|
+
} from '@headlessui/vue'
|
|
68
|
+
import { computed, onBeforeUnmount, onMounted } from 'vue'
|
|
69
|
+
|
|
70
|
+
const emit = defineEmits(['update:show', 'update:searchQuery', 'select'])
|
|
71
|
+
const props = defineProps({
|
|
72
|
+
show: { type: Boolean, default: false },
|
|
73
|
+
searchQuery: { type: String, default: '' },
|
|
74
|
+
groups: { type: Array, default: () => [] },
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
const show = computed({
|
|
78
|
+
get: () => props.show,
|
|
79
|
+
set: (value) => emit('update:show', value),
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
const searchQuery = computed({
|
|
83
|
+
get: () => props.searchQuery,
|
|
84
|
+
set: (value) => emit('update:searchQuery', value),
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
function select(item) {
|
|
88
|
+
emit('select', item)
|
|
89
|
+
show.value = false
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function keydownWatcher(e) {
|
|
93
|
+
if (e.key === 'Escape' && show.value) {
|
|
94
|
+
show.value = false
|
|
95
|
+
e.preventDefault()
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if (
|
|
99
|
+
e.key === 'k' &&
|
|
100
|
+
(e.ctrlKey || e.metaKey) &&
|
|
101
|
+
!e.target.classList.contains('ProseMirror')
|
|
102
|
+
) {
|
|
103
|
+
show.value = true
|
|
104
|
+
e.preventDefault()
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
onMounted(() => window.addEventListener('keydown', keydownWatcher))
|
|
109
|
+
onBeforeUnmount(() => window.removeEventListener('keydown', keydownWatcher))
|
|
110
|
+
</script>
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
defineProps({
|
|
3
|
+
item: { type: Object, required: true },
|
|
4
|
+
active: { type: Boolean, default: false },
|
|
5
|
+
})
|
|
6
|
+
</script>
|
|
7
|
+
|
|
8
|
+
<template>
|
|
9
|
+
<div
|
|
10
|
+
class="flex w-full min-w-0 items-center rounded px-2 py-2 text-base font-medium text-gray-800"
|
|
11
|
+
:class="{ 'bg-gray-200': active }"
|
|
12
|
+
>
|
|
13
|
+
<component
|
|
14
|
+
:is="item.icon"
|
|
15
|
+
v-if="item.icon"
|
|
16
|
+
class="mr-3 h-4 w-4 text-gray-700"
|
|
17
|
+
/>
|
|
18
|
+
<span class="overflow-hidden text-ellipsis whitespace-nowrap">
|
|
19
|
+
{{ item.title }}
|
|
20
|
+
</span>
|
|
21
|
+
<span
|
|
22
|
+
v-if="item.description"
|
|
23
|
+
class="ml-auto whitespace-nowrap pl-2 text-gray-600"
|
|
24
|
+
>
|
|
25
|
+
{{ item.description }}
|
|
26
|
+
</span>
|
|
27
|
+
</div>
|
|
28
|
+
</template>
|