@byyuurin/ui 0.4.0 → 0.5.1
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/README.md +3 -6
- package/dist/module.json +1 -1
- package/dist/module.mjs +3 -2
- package/dist/runtime/components/ContextMenu.vue +65 -0
- package/dist/runtime/components/ContextMenu.vue.d.ts +138 -0
- package/dist/runtime/components/ContextMenuContent.vue +215 -0
- package/dist/runtime/components/ContextMenuContent.vue.d.ts +40 -0
- package/dist/runtime/components/DropdownMenu.vue.d.ts +2 -2
- package/dist/runtime/components/DropdownMenuContent.vue +3 -5
- package/dist/runtime/components/DropdownMenuContent.vue.d.ts +2 -7
- package/dist/runtime/components/NavigationMenu.vue +1 -1
- package/dist/runtime/components/NavigationMenu.vue.d.ts +4 -4
- package/dist/runtime/components/Stepper.vue +13 -11
- package/dist/runtime/components/Stepper.vue.d.ts +6 -1
- package/dist/runtime/components/Timeline.vue +17 -15
- package/dist/runtime/components/Timeline.vue.d.ts +5 -2
- package/dist/runtime/types/index.d.ts +1 -0
- package/dist/runtime/types/index.js +1 -0
- package/dist/runtime/utils/style.d.ts +10 -10
- package/dist/runtime/vue/overrides/none/Link.vue +119 -0
- package/dist/runtime/vue/overrides/none/Link.vue.d.ts +79 -0
- package/dist/runtime/vue/{components → overrides/vue-router}/Link.vue +43 -43
- package/dist/runtime/vue/{components → overrides/vue-router}/Link.vue.d.ts +9 -9
- package/dist/runtime/vue/{stubs.d.ts → stubs/base.d.ts} +1 -2
- package/dist/runtime/vue/{stubs.js → stubs/base.js} +1 -2
- package/dist/runtime/vue/stubs/none.d.ts +56 -0
- package/dist/runtime/vue/stubs/none.js +48 -0
- package/dist/runtime/vue/stubs/vue-router.d.ts +2 -0
- package/dist/runtime/vue/stubs/vue-router.js +2 -0
- package/dist/setup.d.mts +1 -1
- package/dist/shared/{ui.CGCKYv7g.d.mts → ui.Cec0yP1b.d.mts} +11 -2
- package/dist/shared/{ui.DcEKQd0n.mjs → ui.RIHx5Yhe.mjs} +145 -29
- package/dist/unplugin.d.mts +1 -1
- package/dist/unplugin.mjs +66 -23
- package/dist/vite.d.mts +1 -1
- package/dist/vite.mjs +3 -2
- package/package.json +19 -15
package/README.md
CHANGED
|
@@ -13,20 +13,17 @@ https://byyuurin-ui.netlify.app/
|
|
|
13
13
|
## Installation
|
|
14
14
|
|
|
15
15
|
```bash [pnpm]
|
|
16
|
-
pnpm add @byyuurin/ui
|
|
16
|
+
pnpm add @byyuurin/ui unocss
|
|
17
17
|
```
|
|
18
18
|
|
|
19
19
|
```bash [yarn]
|
|
20
|
-
yarn add @byyuurin/ui
|
|
20
|
+
yarn add @byyuurin/ui unocss
|
|
21
21
|
```
|
|
22
22
|
|
|
23
23
|
```bash [npm]
|
|
24
|
-
npm install @byyuurin/ui
|
|
24
|
+
npm install @byyuurin/ui unocss
|
|
25
25
|
```
|
|
26
26
|
|
|
27
|
-
> [!WARNING]
|
|
28
|
-
> If you're using pnpm, ensure that you either set [`shamefully-hoist=true`](https://pnpm.io/settings#shamefully-hoist) in your `.npmrc` file or install `@byyuurin/ui-kit` in your project's root directory.
|
|
29
|
-
|
|
30
27
|
### Nuxt
|
|
31
28
|
|
|
32
29
|
1. Add the UI module in your `nuxt.config.ts`:
|
package/dist/module.json
CHANGED
package/dist/module.mjs
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { defineNuxtModule, createResolver, addPlugin, addComponentsDir, addImportsDir, hasNuxtModule, installModule } from '@nuxt/kit';
|
|
2
2
|
import { defu } from 'defu';
|
|
3
|
-
import { v as version, n as name, a as addTemplates } from './shared/ui.
|
|
3
|
+
import { v as version, n as name, a as addTemplates } from './shared/ui.RIHx5Yhe.mjs';
|
|
4
4
|
import { d as defaultOptions, r as resolveColors, g as getDefaultUIConfig } from './shared/ui.DYMXCXO6.mjs';
|
|
5
|
-
import 'node:
|
|
5
|
+
import 'node:module';
|
|
6
6
|
import 'node:url';
|
|
7
7
|
import '@unocss/config';
|
|
8
8
|
import 'knitwork';
|
|
9
|
+
import 'pathe';
|
|
9
10
|
import 'scule';
|
|
10
11
|
import '@byyuurin/ui-kit';
|
|
11
12
|
import '../dist/runtime/utils/index.js';
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
<script>
|
|
2
|
+
import theme from "#build/ui/context-menu";
|
|
3
|
+
</script>
|
|
4
|
+
|
|
5
|
+
<script setup>
|
|
6
|
+
import { reactivePick } from "@vueuse/core";
|
|
7
|
+
import { ContextMenuRoot, ContextMenuTrigger, useForwardPropsEmits } from "reka-ui";
|
|
8
|
+
import { computed, toRef } from "vue";
|
|
9
|
+
import { useAppConfig } from "#imports";
|
|
10
|
+
import { omit, pick } from "../utils";
|
|
11
|
+
import { cv, merge } from "../utils/style";
|
|
12
|
+
import ContextMenuContent from "./ContextMenuContent.vue";
|
|
13
|
+
const props = defineProps({
|
|
14
|
+
size: { type: null, required: false },
|
|
15
|
+
items: { type: null, required: false },
|
|
16
|
+
checkedIcon: { type: [String, Object], required: false },
|
|
17
|
+
loadingIcon: { type: [String, Object], required: false },
|
|
18
|
+
externalIcon: { type: [Boolean, String, Object], required: false, default: true },
|
|
19
|
+
content: { type: Object, required: false },
|
|
20
|
+
portal: { type: [Boolean, String], required: false, skipCheck: true, default: true },
|
|
21
|
+
labelKey: { type: null, required: false, default: "label" },
|
|
22
|
+
descriptionKey: { type: null, required: false, default: "description" },
|
|
23
|
+
disabled: { type: Boolean, required: false },
|
|
24
|
+
ui: { type: null, required: false },
|
|
25
|
+
class: { type: [Object, String, Number, Boolean, null, Array], required: false, skipCheck: true },
|
|
26
|
+
pressOpenDelay: { type: Number, required: false },
|
|
27
|
+
modal: { type: Boolean, required: false, default: true }
|
|
28
|
+
});
|
|
29
|
+
const emit = defineEmits(["update:open"]);
|
|
30
|
+
const slots = defineSlots();
|
|
31
|
+
const appConfig = useAppConfig();
|
|
32
|
+
const rootProps = useForwardPropsEmits(reactivePick(props, "modal"), emit);
|
|
33
|
+
const contentProps = toRef(() => props.content);
|
|
34
|
+
const getProxySlots = () => omit(slots, ["default"]);
|
|
35
|
+
const ui = computed(() => {
|
|
36
|
+
const styler = cv(merge(theme, appConfig.ui.contextMenu));
|
|
37
|
+
return styler(pick(props, ["size"]));
|
|
38
|
+
});
|
|
39
|
+
</script>
|
|
40
|
+
|
|
41
|
+
<template>
|
|
42
|
+
<ContextMenuRoot v-bind="rootProps">
|
|
43
|
+
<ContextMenuTrigger v-if="!!slots.default" as-child :disabled="props.disabled" :class="props.class">
|
|
44
|
+
<slot></slot>
|
|
45
|
+
</ContextMenuTrigger>
|
|
46
|
+
|
|
47
|
+
<ContextMenuContent
|
|
48
|
+
v-bind="contentProps"
|
|
49
|
+
:items="props.items"
|
|
50
|
+
:portal="props.portal"
|
|
51
|
+
:label-key="props.labelKey"
|
|
52
|
+
:description-key="props.descriptionKey"
|
|
53
|
+
:checked-icon="props.checkedIcon"
|
|
54
|
+
:loading-icon="props.loadingIcon"
|
|
55
|
+
:external-icon="props.externalIcon"
|
|
56
|
+
:ui="ui"
|
|
57
|
+
:ui-override="props.ui"
|
|
58
|
+
:class="ui.content({ class: [props.ui?.content, !slots.default && props.class] })"
|
|
59
|
+
>
|
|
60
|
+
<template v-for="(_, name) in getProxySlots()" #[name]="slotProps">
|
|
61
|
+
<slot :name="name" v-bind="slotProps"></slot>
|
|
62
|
+
</template>
|
|
63
|
+
</ContextMenuContent>
|
|
64
|
+
</ContextMenuRoot>
|
|
65
|
+
</template>
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import type { VariantProps } from '@byyuurin/ui-kit';
|
|
2
|
+
import type { ContextMenuContentEmits, ContextMenuContentProps, ContextMenuRootEmits, ContextMenuRootProps } from 'reka-ui';
|
|
3
|
+
import theme from '#build/ui/context-menu';
|
|
4
|
+
import type { AvatarProps, ComponentBaseProps, ComponentStyler, ComponentUIProps, IconProps, KbdProps, LinkProps } from '../types';
|
|
5
|
+
import type { ArrayOrNested, DynamicSlots, EmitsToProps, GetItemKeys, MergeTypes, NestedItem, StaticSlot } from '../types/utils';
|
|
6
|
+
type ThemeVariants = VariantProps<typeof theme>;
|
|
7
|
+
export interface ContextMenuItem extends Omit<LinkProps, 'type' | 'raw' | 'custom'>, ComponentBaseProps {
|
|
8
|
+
label?: string;
|
|
9
|
+
description?: string;
|
|
10
|
+
icon?: IconProps['name'];
|
|
11
|
+
color?: ThemeVariants['color'];
|
|
12
|
+
avatar?: AvatarProps;
|
|
13
|
+
content?: Omit<ContextMenuContentProps, 'as' | 'asChild' | 'forceMount'> & ComponentBaseProps & Partial<EmitsToProps<ContextMenuContentEmits>>;
|
|
14
|
+
kbds?: KbdProps['value'][] | KbdProps[];
|
|
15
|
+
/**
|
|
16
|
+
* The item type.
|
|
17
|
+
* @default "link"
|
|
18
|
+
*/
|
|
19
|
+
type?: 'label' | 'separator' | 'link' | 'checkbox';
|
|
20
|
+
slot?: string;
|
|
21
|
+
loading?: boolean;
|
|
22
|
+
disabled?: boolean;
|
|
23
|
+
checked?: boolean;
|
|
24
|
+
open?: boolean;
|
|
25
|
+
defaultOpen?: boolean;
|
|
26
|
+
children?: ArrayOrNested<ContextMenuItem>;
|
|
27
|
+
onSelect?: (e: Event) => void;
|
|
28
|
+
onUpdateChecked?: (checked: boolean) => void;
|
|
29
|
+
ui?: Pick<ComponentUIProps<typeof theme>, 'content' | 'item' | 'label' | 'separator' | 'itemLeadingIcon' | 'itemLeadingAvatarSize' | 'itemLeadingAvatar' | 'itemWrapper' | 'itemLabel' | 'itemDescription' | 'itemLabelExternalIcon' | 'itemTrailing' | 'itemTrailingIcon' | 'itemTrailingKbds' | 'itemTrailingKbdsSize'>;
|
|
30
|
+
[key: string]: any;
|
|
31
|
+
}
|
|
32
|
+
export interface ContextMenuProps<T extends ArrayOrNested<ContextMenuItem> = ArrayOrNested<ContextMenuItem>> extends ComponentBaseProps, Omit<ContextMenuRootProps, 'dir'> {
|
|
33
|
+
/** @default "md" */
|
|
34
|
+
size?: ThemeVariants['size'];
|
|
35
|
+
items?: T;
|
|
36
|
+
/**
|
|
37
|
+
* The icon displayed when an item is checked.
|
|
38
|
+
* @default app.icons.check
|
|
39
|
+
*/
|
|
40
|
+
checkedIcon?: IconProps['name'];
|
|
41
|
+
/**
|
|
42
|
+
* The icon displayed when an item is loading.
|
|
43
|
+
* @default app.icons.loading
|
|
44
|
+
*/
|
|
45
|
+
loadingIcon?: IconProps['name'];
|
|
46
|
+
/**
|
|
47
|
+
* The icon displayed when the item is an external link.
|
|
48
|
+
*
|
|
49
|
+
* Set to `false` to hide the external icon.
|
|
50
|
+
* @default app.icons.external
|
|
51
|
+
*/
|
|
52
|
+
externalIcon?: boolean | IconProps['name'];
|
|
53
|
+
/** The content of the menu. */
|
|
54
|
+
content?: Omit<ContextMenuContentProps, 'as' | 'asChild' | 'forceMount'> & Partial<EmitsToProps<ContextMenuContentEmits>>;
|
|
55
|
+
/**
|
|
56
|
+
* Render the menu in a portal.
|
|
57
|
+
* @default true
|
|
58
|
+
*/
|
|
59
|
+
portal?: boolean | string | HTMLElement;
|
|
60
|
+
/**
|
|
61
|
+
* The key used to get the label from the item.
|
|
62
|
+
* @default "label"
|
|
63
|
+
*/
|
|
64
|
+
labelKey?: GetItemKeys<T>;
|
|
65
|
+
/**
|
|
66
|
+
* The key used to get the description from the item.
|
|
67
|
+
* @default "description"
|
|
68
|
+
*/
|
|
69
|
+
descriptionKey?: GetItemKeys<T>;
|
|
70
|
+
disabled?: boolean;
|
|
71
|
+
ui?: ComponentUIProps<typeof theme>;
|
|
72
|
+
}
|
|
73
|
+
export interface ContextMenuEmits extends ContextMenuRootEmits {
|
|
74
|
+
}
|
|
75
|
+
export type ContextMenuSlots<A extends ArrayOrNested<ContextMenuItem> = ArrayOrNested<ContextMenuItem>, T extends NestedItem<A> = NestedItem<A>> = {
|
|
76
|
+
'default': StaticSlot;
|
|
77
|
+
'item': StaticSlot<{
|
|
78
|
+
item: T;
|
|
79
|
+
active?: boolean;
|
|
80
|
+
index: number;
|
|
81
|
+
ui: ComponentStyler<typeof theme>;
|
|
82
|
+
}>;
|
|
83
|
+
'item-leading': StaticSlot<{
|
|
84
|
+
item: T;
|
|
85
|
+
active?: boolean;
|
|
86
|
+
index: number;
|
|
87
|
+
ui: ComponentStyler<typeof theme>;
|
|
88
|
+
}>;
|
|
89
|
+
'item-label': StaticSlot<{
|
|
90
|
+
item: T;
|
|
91
|
+
active?: boolean;
|
|
92
|
+
index: number;
|
|
93
|
+
}>;
|
|
94
|
+
'item-description': StaticSlot<{
|
|
95
|
+
item: T;
|
|
96
|
+
active?: boolean;
|
|
97
|
+
index: number;
|
|
98
|
+
}>;
|
|
99
|
+
'item-trailing': StaticSlot<{
|
|
100
|
+
item: T;
|
|
101
|
+
active?: boolean;
|
|
102
|
+
index: number;
|
|
103
|
+
ui: ComponentStyler<typeof theme>;
|
|
104
|
+
}>;
|
|
105
|
+
'content-top': StaticSlot<{
|
|
106
|
+
sub: boolean;
|
|
107
|
+
}>;
|
|
108
|
+
'content-bottom': StaticSlot<{
|
|
109
|
+
sub: boolean;
|
|
110
|
+
}>;
|
|
111
|
+
} & DynamicSlots<MergeTypes<T>, 'label' | 'description', {
|
|
112
|
+
active?: boolean;
|
|
113
|
+
index: number;
|
|
114
|
+
}> & DynamicSlots<MergeTypes<T>, 'leading' | 'trailing', {
|
|
115
|
+
active?: boolean;
|
|
116
|
+
index: number;
|
|
117
|
+
ui: ComponentStyler<typeof theme>;
|
|
118
|
+
}>;
|
|
119
|
+
declare const _default: typeof __VLS_export;
|
|
120
|
+
export default _default;
|
|
121
|
+
declare const __VLS_export: <T extends ArrayOrNested<ContextMenuItem>>(__VLS_props: NonNullable<Awaited<typeof __VLS_setup>>["props"], __VLS_ctx?: __VLS_PrettifyLocal<Pick<NonNullable<Awaited<typeof __VLS_setup>>, "attrs" | "emit" | "slots">>, __VLS_exposed?: NonNullable<Awaited<typeof __VLS_setup>>["expose"], __VLS_setup?: Promise<{
|
|
122
|
+
props: import("vue").PublicProps & __VLS_PrettifyLocal<ContextMenuProps<T> & {
|
|
123
|
+
"onUpdate:open"?: ((payload: boolean) => any) | undefined;
|
|
124
|
+
}> & (typeof globalThis extends {
|
|
125
|
+
__VLS_PROPS_FALLBACK: infer P;
|
|
126
|
+
} ? P : {});
|
|
127
|
+
expose: (exposed: {}) => void;
|
|
128
|
+
attrs: any;
|
|
129
|
+
slots: ContextMenuSlots<T, NestedItem<T>>;
|
|
130
|
+
emit: (evt: "update:open", payload: boolean) => void;
|
|
131
|
+
}>) => import("vue").VNode & {
|
|
132
|
+
__ctx?: Awaited<typeof __VLS_setup>;
|
|
133
|
+
};
|
|
134
|
+
type __VLS_PrettifyLocal<T> = (T extends any ? {
|
|
135
|
+
[K in keyof T]: T[K];
|
|
136
|
+
} : {
|
|
137
|
+
[K in keyof T as K]: T[K];
|
|
138
|
+
}) & {};
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
<script>
|
|
2
|
+
|
|
3
|
+
</script>
|
|
4
|
+
|
|
5
|
+
<script setup>
|
|
6
|
+
import { createReusableTemplate, reactiveOmit } from "@vueuse/core";
|
|
7
|
+
import { useForwardPropsEmits } from "reka-ui";
|
|
8
|
+
import { ContextMenu } from "reka-ui/namespaced";
|
|
9
|
+
import { computed, toRef } from "vue";
|
|
10
|
+
import { useAppConfig } from "#imports";
|
|
11
|
+
import { useLocale } from "../composables/useLocale";
|
|
12
|
+
import { usePortal } from "../composables/usePortal";
|
|
13
|
+
import { get, isArrayOfArray, omit } from "../utils";
|
|
14
|
+
import { pickLinkProps } from "../utils/link";
|
|
15
|
+
import Avatar from "./Avatar.vue";
|
|
16
|
+
import ContextMenuContent from "./ContextMenuContent.vue";
|
|
17
|
+
import Icon from "./Icon.vue";
|
|
18
|
+
import Kbd from "./Kbd.vue";
|
|
19
|
+
import Link from "./Link.vue";
|
|
20
|
+
import LinkBase from "./LinkBase.vue";
|
|
21
|
+
defineOptions({ inheritAttrs: false });
|
|
22
|
+
const props = defineProps({
|
|
23
|
+
items: { type: null, required: false },
|
|
24
|
+
portal: { type: [Boolean, String], required: false, skipCheck: true },
|
|
25
|
+
sub: { type: Boolean, required: false },
|
|
26
|
+
labelKey: { type: null, required: true },
|
|
27
|
+
descriptionKey: { type: null, required: true },
|
|
28
|
+
checkedIcon: { type: [String, Object], required: false },
|
|
29
|
+
loadingIcon: { type: [String, Object], required: false },
|
|
30
|
+
externalIcon: { type: [Boolean, String, Object], required: false },
|
|
31
|
+
ui: { type: null, required: true },
|
|
32
|
+
uiOverride: { type: null, required: false },
|
|
33
|
+
class: { type: [Object, String, Number, Boolean, null, Array], required: false, skipCheck: true },
|
|
34
|
+
loop: { type: Boolean, required: false },
|
|
35
|
+
sideFlip: { type: Boolean, required: false },
|
|
36
|
+
alignOffset: { type: Number, required: false },
|
|
37
|
+
alignFlip: { type: Boolean, required: false },
|
|
38
|
+
avoidCollisions: { type: Boolean, required: false },
|
|
39
|
+
collisionBoundary: { type: null, required: false },
|
|
40
|
+
collisionPadding: { type: [Number, Object], required: false },
|
|
41
|
+
sticky: { type: String, required: false },
|
|
42
|
+
hideWhenDetached: { type: Boolean, required: false },
|
|
43
|
+
positionStrategy: { type: String, required: false },
|
|
44
|
+
disableUpdateOnLayoutShift: { type: Boolean, required: false },
|
|
45
|
+
prioritizePosition: { type: Boolean, required: false },
|
|
46
|
+
reference: { type: null, required: false }
|
|
47
|
+
});
|
|
48
|
+
const emit = defineEmits(["escapeKeyDown", "pointerDownOutside", "focusOutside", "interactOutside", "closeAutoFocus"]);
|
|
49
|
+
const slots = defineSlots();
|
|
50
|
+
const { dir } = useLocale();
|
|
51
|
+
const appConfig = useAppConfig();
|
|
52
|
+
const portalProps = usePortal(toRef(() => props.portal));
|
|
53
|
+
const contentProps = useForwardPropsEmits(reactiveOmit(props, "sub", "items", "portal", "labelKey", "descriptionKey", "checkedIcon", "loadingIcon", "externalIcon", "class", "ui", "uiOverride"), emit);
|
|
54
|
+
const getProxySlots = () => omit(slots, ["default"]);
|
|
55
|
+
const [DefineItemTemplate, ReuseItemTemplate] = createReusableTemplate();
|
|
56
|
+
const childrenIcon = computed(() => dir.value === "rtl" ? appConfig.ui.icons.chevronLeft : appConfig.ui.icons.chevronRight);
|
|
57
|
+
const groups = computed(
|
|
58
|
+
() => props.items?.length ? isArrayOfArray(props.items) ? props.items : [props.items] : []
|
|
59
|
+
);
|
|
60
|
+
</script>
|
|
61
|
+
|
|
62
|
+
<template>
|
|
63
|
+
<DefineItemTemplate v-slot="{ item, active, index }">
|
|
64
|
+
<slot :name="item.slot || 'item'" :item="item" :index="index" :ui="ui">
|
|
65
|
+
<slot :name="`${item.slot || 'item'}-leading`" :item="item" :active="active" :index="index" :ui="ui">
|
|
66
|
+
<Icon
|
|
67
|
+
v-if="item.loading"
|
|
68
|
+
:name="props.loadingIcon || appConfig.ui.icons.loading"
|
|
69
|
+
:class="props.ui.itemLeadingIcon({ class: [props.uiOverride?.itemLeadingIcon, item.ui?.itemLeadingIcon], color: item.color, loading: true })"
|
|
70
|
+
data-part="itemLeadingIcon"
|
|
71
|
+
/>
|
|
72
|
+
<Icon
|
|
73
|
+
v-else-if="item.icon"
|
|
74
|
+
:name="item.icon"
|
|
75
|
+
:class="props.ui.itemLeadingIcon({ class: [props.uiOverride?.itemLeadingIcon, item.ui?.itemLeadingIcon], color: item.color, active })"
|
|
76
|
+
data-part="itemLeadingIcon"
|
|
77
|
+
/>
|
|
78
|
+
<Avatar
|
|
79
|
+
v-else-if="item.avatar"
|
|
80
|
+
v-bind="item.avatar"
|
|
81
|
+
:size="item.ui?.itemLeadingAvatarSize || props.uiOverride?.itemLeadingAvatarSize || props.ui.itemLeadingAvatarSize()"
|
|
82
|
+
:class="props.ui.itemLeadingAvatar({ class: [props.uiOverride?.itemLeadingAvatar, item.ui?.itemLeadingAvatar], active })"
|
|
83
|
+
data-part="itemLeadingAvatar"
|
|
84
|
+
/>
|
|
85
|
+
</slot>
|
|
86
|
+
|
|
87
|
+
<span
|
|
88
|
+
v-if="get(item, props.labelKey) || !!slots[`${item.slot || 'item'}-label`] || (get(item, props.descriptionKey) || !!slots[`${item.slot || 'item'}-description`])"
|
|
89
|
+
:class="props.ui.itemWrapper({ class: [props.uiOverride?.itemWrapper, item.ui?.itemWrapper] })"
|
|
90
|
+
data-part="itemWrapper"
|
|
91
|
+
>
|
|
92
|
+
<span
|
|
93
|
+
v-if="get(item, props.labelKey) || !!slots[`${item.slot || 'item'}-label`]"
|
|
94
|
+
:class="props.ui.itemLabel({ class: [props.uiOverride?.itemLabel, item.ui?.itemLabel], active })"
|
|
95
|
+
data-part="itemLabel"
|
|
96
|
+
>
|
|
97
|
+
<slot :name="`${item.slot || 'item'}-label`" :item="item" :active="active" :index="index">
|
|
98
|
+
{{ get(item, props.labelKey) }}
|
|
99
|
+
</slot>
|
|
100
|
+
|
|
101
|
+
<Icon
|
|
102
|
+
v-if="item.target === '_blank' && props.externalIcon !== false"
|
|
103
|
+
:name="typeof props.externalIcon === 'string' ? props.externalIcon : appConfig.ui.icons.external"
|
|
104
|
+
:class="props.ui.itemLabelExternalIcon({ class: [props.uiOverride?.itemLabelExternalIcon, item.ui?.itemLabelExternalIcon], color: item.color, active })"
|
|
105
|
+
data-part="itemLabelExternalIcon"
|
|
106
|
+
/>
|
|
107
|
+
</span>
|
|
108
|
+
|
|
109
|
+
<span v-if="get(item, props.descriptionKey) || !!slots[`${item.slot || 'item'}-description`]" :class="props.ui.itemDescription({ class: [props.uiOverride?.itemDescription, item.ui?.itemDescription] })" data-part="itemDescription">
|
|
110
|
+
<slot :name="`${item.slot || 'item'}-description`" :item="item" :active="active" :index="index">
|
|
111
|
+
{{ get(item, props.descriptionKey) }}
|
|
112
|
+
</slot>
|
|
113
|
+
</span>
|
|
114
|
+
</span>
|
|
115
|
+
|
|
116
|
+
<span :class="props.ui.itemTrailing({ class: [props.uiOverride?.itemTrailing, item.ui?.itemTrailing] })" data-part="itemTrailing">
|
|
117
|
+
<slot :name="`${item.slot || 'item'}-trailing`" :item="item" :active="active" :index="index" :ui="ui">
|
|
118
|
+
<Icon v-if="item.children?.length" :name="childrenIcon" :class="props.ui.itemTrailingIcon({ class: [props.uiOverride?.itemTrailingIcon, item.ui?.itemTrailingIcon], color: item.color, active })" data-part="itemTrailingIcon" />
|
|
119
|
+
<span v-else-if="item.kbds?.length" :class="props.ui.itemTrailingKbds({ class: [props.uiOverride?.itemTrailingKbds, item.ui?.itemTrailingKbds] })" data-part="itemTrailingKbds">
|
|
120
|
+
<Kbd
|
|
121
|
+
v-for="(kbd, kbdIndex) in item.kbds"
|
|
122
|
+
:key="kbdIndex"
|
|
123
|
+
:size="item.ui?.itemTrailingKbdsSize || props.uiOverride?.itemTrailingKbdsSize || ui.itemTrailingKbdsSize()"
|
|
124
|
+
v-bind="typeof kbd === 'string' ? { value: kbd } : kbd"
|
|
125
|
+
/>
|
|
126
|
+
</span>
|
|
127
|
+
</slot>
|
|
128
|
+
|
|
129
|
+
<ContextMenu.ItemIndicator as-child>
|
|
130
|
+
<Icon :name="props.checkedIcon || appConfig.ui.icons.check" :class="props.ui.itemTrailingIcon({ class: [props.uiOverride?.itemTrailingIcon, item.ui?.itemTrailingIcon], color: item.color })" data-part="itemTrailingIcon" />
|
|
131
|
+
</ContextMenu.ItemIndicator>
|
|
132
|
+
</span>
|
|
133
|
+
</slot>
|
|
134
|
+
</DefineItemTemplate>
|
|
135
|
+
|
|
136
|
+
<ContextMenu.Portal v-bind="portalProps">
|
|
137
|
+
<component :is="props.sub ? ContextMenu.SubContent : ContextMenu.Content" :class="props.ui.content({ class: [props.uiOverride?.content, props.class] })" v-bind="contentProps">
|
|
138
|
+
<slot name="content-top" :sub="props.sub ?? false"></slot>
|
|
139
|
+
|
|
140
|
+
<div role="presentation" :class="props.ui.viewport({ class: props.uiOverride?.viewport })" data-part="viewport">
|
|
141
|
+
<ContextMenu.Group v-for="(group, groupIndex) in groups" :key="`group-${groupIndex}`" :class="props.ui.group({ class: props.uiOverride?.group })" data-part="group">
|
|
142
|
+
<template v-for="(item, index) in group" :key="`group-${groupIndex}-${index}`">
|
|
143
|
+
<ContextMenu.Label v-if="item.type === 'label'" :class="props.ui.label({ class: [props.uiOverride?.label, item.ui?.label, item.class] })" data-part="label">
|
|
144
|
+
<ReuseItemTemplate :item="item" :index="index" />
|
|
145
|
+
</ContextMenu.Label>
|
|
146
|
+
<ContextMenu.Separator v-else-if="item.type === 'separator'" :class="props.ui.separator({ class: [props.uiOverride?.separator, item.ui?.separator, item.class] })" data-part="separator" />
|
|
147
|
+
<ContextMenu.Sub v-else-if="item?.children?.length" :open="item.open" :default-open="item.defaultOpen">
|
|
148
|
+
<ContextMenu.SubTrigger
|
|
149
|
+
as="button"
|
|
150
|
+
type="button"
|
|
151
|
+
:disabled="item.disabled"
|
|
152
|
+
:text-value="get(item, props.labelKey)"
|
|
153
|
+
:class="props.ui.item({ class: [props.uiOverride?.item, item.ui?.item, item.class], color: item.color })"
|
|
154
|
+
data-part="item"
|
|
155
|
+
>
|
|
156
|
+
<ReuseItemTemplate :item="item" :index="index" />
|
|
157
|
+
</ContextMenu.SubTrigger>
|
|
158
|
+
|
|
159
|
+
<ContextMenuContent
|
|
160
|
+
sub
|
|
161
|
+
:class="item.ui?.content"
|
|
162
|
+
:ui="props.ui"
|
|
163
|
+
:ui-override="props.uiOverride"
|
|
164
|
+
:portal="props.portal"
|
|
165
|
+
:items="item.children"
|
|
166
|
+
side="right"
|
|
167
|
+
align="start"
|
|
168
|
+
:align-offset="-4"
|
|
169
|
+
:side-offset="3"
|
|
170
|
+
:label-key="props.labelKey"
|
|
171
|
+
:description-key="props.descriptionKey"
|
|
172
|
+
:checked-icon="props.checkedIcon"
|
|
173
|
+
:loading-icon="props.loadingIcon"
|
|
174
|
+
:external-icon="props.externalIcon"
|
|
175
|
+
v-bind="item.content"
|
|
176
|
+
>
|
|
177
|
+
<template v-for="(_, name) in getProxySlots()" #[name]="slotProps">
|
|
178
|
+
<slot :name="name" v-bind="slotProps"></slot>
|
|
179
|
+
</template>
|
|
180
|
+
</ContextMenuContent>
|
|
181
|
+
</ContextMenu.Sub>
|
|
182
|
+
<ContextMenu.CheckboxItem
|
|
183
|
+
v-else-if="item.type === 'checkbox'"
|
|
184
|
+
:model-value="item.checked"
|
|
185
|
+
:disabled="item.disabled"
|
|
186
|
+
:text-value="get(item, props.labelKey)"
|
|
187
|
+
:class="props.ui.item({ class: [props.uiOverride?.item, item.ui?.item, item.class], color: item.color })"
|
|
188
|
+
data-part="item"
|
|
189
|
+
@update:model-value="item.onUpdateChecked"
|
|
190
|
+
@select="item.onSelect"
|
|
191
|
+
>
|
|
192
|
+
<ReuseItemTemplate :item="item" :index="index" />
|
|
193
|
+
</ContextMenu.CheckboxItem>
|
|
194
|
+
<Link v-else v-slot="{ active, ...slotProps }" v-bind="pickLinkProps(item)" custom>
|
|
195
|
+
<ContextMenu.Item
|
|
196
|
+
as-child
|
|
197
|
+
:disabled="item.disabled"
|
|
198
|
+
:text-value="get(item, props.labelKey)"
|
|
199
|
+
@select="item.onSelect"
|
|
200
|
+
>
|
|
201
|
+
<LinkBase v-bind="slotProps" :class="props.ui.item({ class: [props.uiOverride?.item, item.ui?.item, item.class], color: item.color, active })" data-part="item">
|
|
202
|
+
<ReuseItemTemplate :item="item" :active="active" :index="index" />
|
|
203
|
+
</LinkBase>
|
|
204
|
+
</ContextMenu.Item>
|
|
205
|
+
</Link>
|
|
206
|
+
</template>
|
|
207
|
+
</ContextMenu.Group>
|
|
208
|
+
</div>
|
|
209
|
+
|
|
210
|
+
<slot></slot>
|
|
211
|
+
|
|
212
|
+
<slot name="content-bottom" :sub="props.sub ?? false"></slot>
|
|
213
|
+
</component>
|
|
214
|
+
</ContextMenu.Portal>
|
|
215
|
+
</template>
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { ContextMenuContentProps as RekaContextMenuContentProps } from 'reka-ui';
|
|
2
|
+
import type theme from '#build/ui/context-menu';
|
|
3
|
+
import type { ComponentBaseProps, ComponentStyler, ComponentUIProps, ContextMenuItem, ContextMenuSlots, IconProps } from '../types';
|
|
4
|
+
import type { ArrayOrNested, GetItemKeys } from '../types/utils';
|
|
5
|
+
interface ContextMenuContentProps<T extends ArrayOrNested<ContextMenuItem>> extends ComponentBaseProps, Omit<RekaContextMenuContentProps, 'as' | 'asChild' | 'forceMount'> {
|
|
6
|
+
items?: T;
|
|
7
|
+
portal?: boolean | string | HTMLElement;
|
|
8
|
+
sub?: boolean;
|
|
9
|
+
labelKey: GetItemKeys<T>;
|
|
10
|
+
descriptionKey: GetItemKeys<T>;
|
|
11
|
+
checkedIcon?: IconProps['name'];
|
|
12
|
+
loadingIcon?: IconProps['name'];
|
|
13
|
+
externalIcon?: boolean | IconProps['name'];
|
|
14
|
+
ui: ComponentStyler<typeof theme>;
|
|
15
|
+
uiOverride?: ComponentUIProps<typeof theme>;
|
|
16
|
+
}
|
|
17
|
+
declare const _default: typeof __VLS_export;
|
|
18
|
+
export default _default;
|
|
19
|
+
declare const __VLS_export: <T extends ArrayOrNested<ContextMenuItem>>(__VLS_props: NonNullable<Awaited<typeof __VLS_setup>>["props"], __VLS_ctx?: __VLS_PrettifyLocal<Pick<NonNullable<Awaited<typeof __VLS_setup>>, "attrs" | "emit" | "slots">>, __VLS_exposed?: NonNullable<Awaited<typeof __VLS_setup>>["expose"], __VLS_setup?: Promise<{
|
|
20
|
+
props: import("vue").PublicProps & __VLS_PrettifyLocal<ContextMenuContentProps<T> & {
|
|
21
|
+
onEscapeKeyDown?: ((event: KeyboardEvent) => any) | undefined;
|
|
22
|
+
onPointerDownOutside?: ((event: import("reka-ui").PointerDownOutsideEvent) => any) | undefined;
|
|
23
|
+
onFocusOutside?: ((event: import("reka-ui").FocusOutsideEvent) => any) | undefined;
|
|
24
|
+
onInteractOutside?: ((event: import("reka-ui").PointerDownOutsideEvent | import("reka-ui").FocusOutsideEvent) => any) | undefined;
|
|
25
|
+
onCloseAutoFocus?: ((event: Event) => any) | undefined;
|
|
26
|
+
}> & (typeof globalThis extends {
|
|
27
|
+
__VLS_PROPS_FALLBACK: infer P;
|
|
28
|
+
} ? P : {});
|
|
29
|
+
expose: (exposed: {}) => void;
|
|
30
|
+
attrs: any;
|
|
31
|
+
slots: ContextMenuSlots<T>;
|
|
32
|
+
emit: ((evt: "escapeKeyDown", event: KeyboardEvent) => void) & ((evt: "pointerDownOutside", event: import("reka-ui").PointerDownOutsideEvent) => void) & ((evt: "focusOutside", event: import("reka-ui").FocusOutsideEvent) => void) & ((evt: "interactOutside", event: import("reka-ui").PointerDownOutsideEvent | import("reka-ui").FocusOutsideEvent) => void) & ((evt: "closeAutoFocus", event: Event) => void);
|
|
33
|
+
}>) => import("vue").VNode & {
|
|
34
|
+
__ctx?: Awaited<typeof __VLS_setup>;
|
|
35
|
+
};
|
|
36
|
+
type __VLS_PrettifyLocal<T> = (T extends any ? {
|
|
37
|
+
[K in keyof T]: T[K];
|
|
38
|
+
} : {
|
|
39
|
+
[K in keyof T as K]: T[K];
|
|
40
|
+
}) & {};
|
|
@@ -9,7 +9,7 @@ export interface DropdownMenuItem extends Omit<LinkProps, 'type' | 'raw' | 'cust
|
|
|
9
9
|
icon?: IconProps['name'];
|
|
10
10
|
color?: ThemeVariants['color'];
|
|
11
11
|
avatar?: AvatarProps;
|
|
12
|
-
content?: Omit<DropdownMenuContentProps, 'as' | 'asChild' | 'forceMount'> & Partial<EmitsToProps<DropdownMenuContentEmits>>;
|
|
12
|
+
content?: Omit<DropdownMenuContentProps, 'as' | 'asChild' | 'forceMount'> & ComponentBaseProps & Partial<EmitsToProps<DropdownMenuContentEmits>>;
|
|
13
13
|
kbds?: KbdProps['value'][] | KbdProps[];
|
|
14
14
|
/**
|
|
15
15
|
* The item type.
|
|
@@ -25,7 +25,7 @@ export interface DropdownMenuItem extends Omit<LinkProps, 'type' | 'raw' | 'cust
|
|
|
25
25
|
children?: ArrayOrNested<DropdownMenuItem>;
|
|
26
26
|
onSelect?: (e: Event) => void;
|
|
27
27
|
onUpdateChecked?: (checked: boolean) => void;
|
|
28
|
-
ui?: Pick<ComponentUIProps<typeof theme>, 'item' | 'label' | 'separator' | 'itemLeadingIcon' | 'itemLeadingAvatarSize' | 'itemLeadingAvatar' | 'itemWrapper' | 'itemLabel' | 'itemDescription' | 'itemLabelExternalIcon' | 'itemTrailing' | 'itemTrailingIcon' | 'itemTrailingKbds' | 'itemTrailingKbdsSize'>;
|
|
28
|
+
ui?: Pick<ComponentUIProps<typeof theme>, 'content' | 'item' | 'label' | 'separator' | 'itemLeadingIcon' | 'itemLeadingAvatarSize' | 'itemLeadingAvatar' | 'itemWrapper' | 'itemLabel' | 'itemDescription' | 'itemLabelExternalIcon' | 'itemTrailing' | 'itemTrailingIcon' | 'itemTrailingKbds' | 'itemTrailingKbdsSize'>;
|
|
29
29
|
[key: string]: any;
|
|
30
30
|
}
|
|
31
31
|
type ThemeVariants = VariantProps<typeof theme>;
|
|
@@ -20,7 +20,6 @@ import Link from "./Link.vue";
|
|
|
20
20
|
import LinkBase from "./LinkBase.vue";
|
|
21
21
|
defineOptions({ inheritAttrs: false });
|
|
22
22
|
const props = defineProps({
|
|
23
|
-
size: { type: null, required: false },
|
|
24
23
|
items: { type: null, required: false },
|
|
25
24
|
portal: { type: [Boolean, String], required: false, skipCheck: true },
|
|
26
25
|
sub: { type: Boolean, required: false },
|
|
@@ -83,8 +82,8 @@ const groups = computed(
|
|
|
83
82
|
/>
|
|
84
83
|
<Avatar
|
|
85
84
|
v-else-if="item.avatar"
|
|
85
|
+
:size="item.ui?.itemLeadingAvatarSize || props.uiOverride?.itemLeadingAvatarSize || props.ui.itemLeadingAvatarSize()"
|
|
86
86
|
v-bind="item.avatar"
|
|
87
|
-
:size="item.avatar.size || props.size"
|
|
88
87
|
:class="props.ui.itemLeadingAvatar({ class: [props.uiOverride?.itemLeadingAvatar, item.ui?.itemLeadingAvatar], active })"
|
|
89
88
|
data-part="itemLeadingAvatar"
|
|
90
89
|
/>
|
|
@@ -140,7 +139,7 @@ const groups = computed(
|
|
|
140
139
|
</DefineItemTemplate>
|
|
141
140
|
|
|
142
141
|
<DropdownMenu.Portal v-bind="portalProps">
|
|
143
|
-
<component :is="props.sub ? DropdownMenu.SubContent : DropdownMenu.Content"
|
|
142
|
+
<component :is="props.sub ? DropdownMenu.SubContent : DropdownMenu.Content" :class="props.ui.content({ class: [props.uiOverride?.content, props.class] })" v-bind="contentProps">
|
|
144
143
|
<slot name="content-top" :sub="props.sub ?? false"></slot>
|
|
145
144
|
|
|
146
145
|
<div role="presentation" :class="props.ui.viewport({ class: props.uiOverride?.viewport })" data-part="viewport">
|
|
@@ -164,12 +163,11 @@ const groups = computed(
|
|
|
164
163
|
|
|
165
164
|
<DropdownMenuContent
|
|
166
165
|
sub
|
|
167
|
-
:class="
|
|
166
|
+
:class="item.ui?.content"
|
|
168
167
|
:ui="props.ui"
|
|
169
168
|
:ui-override="props.uiOverride"
|
|
170
169
|
:portal="props.portal"
|
|
171
170
|
:items="item.children"
|
|
172
|
-
side="right"
|
|
173
171
|
align="start"
|
|
174
172
|
:align-offset="-4"
|
|
175
173
|
:side-offset="3"
|
|
@@ -1,11 +1,8 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
import type { DropdownMenuContentEmits as RekaDropdownMenuContentEmits, DropdownMenuContentProps as RekaDropdownMenuContentProps } from 'reka-ui';
|
|
1
|
+
import type { DropdownMenuContentProps as RekaDropdownMenuContentProps } from 'reka-ui';
|
|
3
2
|
import type theme from '#build/ui/dropdown-menu';
|
|
4
3
|
import type { ComponentBaseProps, ComponentStyler, ComponentUIProps, DropdownMenuItem, DropdownMenuSlots, IconProps } from '../types';
|
|
5
4
|
import type { ArrayOrNested, DynamicSlots, GetItemKeys, MergeTypes, NestedItem, StaticSlot } from '../types/utils';
|
|
6
|
-
type ThemeVariants = VariantProps<typeof theme>;
|
|
7
5
|
export interface DropdownMenuContentProps<T extends ArrayOrNested<DropdownMenuItem>> extends ComponentBaseProps, Omit<RekaDropdownMenuContentProps, 'as' | 'asChild' | 'forceMount'> {
|
|
8
|
-
size?: ThemeVariants['size'];
|
|
9
6
|
items?: T;
|
|
10
7
|
portal?: boolean | string | HTMLElement;
|
|
11
8
|
sub?: boolean;
|
|
@@ -17,9 +14,7 @@ export interface DropdownMenuContentProps<T extends ArrayOrNested<DropdownMenuIt
|
|
|
17
14
|
ui: ComponentStyler<typeof theme>;
|
|
18
15
|
uiOverride?: ComponentUIProps<typeof theme>;
|
|
19
16
|
}
|
|
20
|
-
|
|
21
|
-
}
|
|
22
|
-
export type DropdownMenuContentSlots<A extends ArrayOrNested<DropdownMenuItem> = ArrayOrNested<DropdownMenuItem>, T extends NestedItem<A> = NestedItem<A>> = Pick<DropdownMenuSlots<A>, 'item' | 'item-leading' | 'item-label' | 'item-description' | 'item-trailing' | 'content-top' | 'content-bottom'> & {
|
|
17
|
+
type DropdownMenuContentSlots<A extends ArrayOrNested<DropdownMenuItem> = ArrayOrNested<DropdownMenuItem>, T extends NestedItem<A> = NestedItem<A>> = Pick<DropdownMenuSlots<A>, 'item' | 'item-leading' | 'item-label' | 'item-description' | 'item-trailing' | 'content-top' | 'content-bottom'> & {
|
|
23
18
|
default: StaticSlot;
|
|
24
19
|
} & DynamicSlots<MergeTypes<T>, 'label' | 'description', {
|
|
25
20
|
active: boolean;
|
|
@@ -291,7 +291,7 @@ function getAccordionDefaultValue(list, level = 0) {
|
|
|
291
291
|
:item="childItem"
|
|
292
292
|
:index="childIndex"
|
|
293
293
|
:level="level + 1"
|
|
294
|
-
:class="ui.childItem({ class: [props.ui?.childItem,
|
|
294
|
+
:class="ui.childItem({ class: [props.ui?.childItem, childItem.ui?.childItem] })"
|
|
295
295
|
data-part="childItem"
|
|
296
296
|
/>
|
|
297
297
|
</AccordionRoot>
|
|
@@ -13,8 +13,8 @@ export interface NavigationMenuItem extends ComponentBaseProps, Omit<LinkProps,
|
|
|
13
13
|
icon?: IconProps['name'];
|
|
14
14
|
avatar?: AvatarProps;
|
|
15
15
|
/**
|
|
16
|
-
* Display a
|
|
17
|
-
* `{ size: '
|
|
16
|
+
* Display a badge on the item.
|
|
17
|
+
* `{ size: 'sm', color: 'neutral', variant: 'outline' }`
|
|
18
18
|
*/
|
|
19
19
|
badge?: string | number | BadgeProps;
|
|
20
20
|
/**
|
|
@@ -54,9 +54,9 @@ export interface NavigationMenuItem extends ComponentBaseProps, Omit<LinkProps,
|
|
|
54
54
|
}
|
|
55
55
|
type ThemeVariants = VariantProps<typeof theme>;
|
|
56
56
|
type SingleOrMultipleType = 'single' | 'multiple';
|
|
57
|
-
type Orientation =
|
|
57
|
+
type Orientation = NavigationMenuRootProps['orientation'];
|
|
58
58
|
type NavigationMenuModelValue<K extends SingleOrMultipleType = SingleOrMultipleType, O extends Orientation = Orientation> = O extends 'horizontal' ? string : K extends 'single' ? string : K extends 'multiple' ? string[] : MaybeArray<string>;
|
|
59
|
-
export interface NavigationMenuProps<T extends ArrayOrNested<NavigationMenuItem> = ArrayOrNested<NavigationMenuItem>, K extends SingleOrMultipleType = SingleOrMultipleType, O extends Orientation = Orientation> extends ComponentBaseProps, Pick<NavigationMenuRootProps, 'delayDuration' | 'disableClickTrigger' | 'disableHoverTrigger' | 'skipDelayDuration' | 'disablePointerLeaveClose' | 'unmountOnHide'>, Pick<AccordionRootProps, 'disabled' | '
|
|
59
|
+
export interface NavigationMenuProps<T extends ArrayOrNested<NavigationMenuItem> = ArrayOrNested<NavigationMenuItem>, K extends SingleOrMultipleType = SingleOrMultipleType, O extends Orientation = Orientation> extends ComponentBaseProps, Pick<NavigationMenuRootProps, 'delayDuration' | 'disableClickTrigger' | 'disableHoverTrigger' | 'skipDelayDuration' | 'disablePointerLeaveClose' | 'unmountOnHide'>, Pick<AccordionRootProps, 'disabled' | 'collapsible'> {
|
|
60
60
|
/**
|
|
61
61
|
* The element or component this component should render as.
|
|
62
62
|
* @default "div"
|