diff --git a/playground/app/pages/components/input-menu.vue b/playground/app/pages/components/input-menu.vue index f946be1ede..eec00ebd11 100644 --- a/playground/app/pages/components/input-menu.vue +++ b/playground/app/pages/components/input-menu.vue @@ -17,18 +17,23 @@ const selectedItems = ref([fruits[0]!, vegetables[0]!]) const statuses = [{ label: 'Backlog', + description: 'Issues that have been identified but not yet prioritized', icon: 'i-lucide-circle-help' }, { label: 'Todo', + description: 'Issues that are ready to be worked on', icon: 'i-lucide-circle-plus' }, { label: 'In Progress', + description: 'Issues that are currently being worked on', icon: 'i-lucide-circle-arrow-up' }, { label: 'Done', + description: 'Issues that have been completed successfully', icon: 'i-lucide-circle-check' }, { label: 'Canceled', + description: 'Issues that have been cancelled or rejected', icon: 'i-lucide-circle-x' }] satisfies InputMenuItem[] diff --git a/playground/app/pages/components/select-menu.vue b/playground/app/pages/components/select-menu.vue index ff64cd2fb1..746b189848 100644 --- a/playground/app/pages/components/select-menu.vue +++ b/playground/app/pages/components/select-menu.vue @@ -18,22 +18,27 @@ const selectedItems = ref([fruits[0]!, vegetables[0]!]) const statuses = [{ label: 'Backlog', value: 'backlog', + description: 'Issues that have been identified but not yet prioritized', icon: 'i-lucide-circle-help' }, { label: 'Todo', value: 'todo', + description: 'Issues that are ready to be worked on', icon: 'i-lucide-circle-plus' }, { label: 'In Progress', value: 'in_progress', + description: 'Issues that are currently being worked on', icon: 'i-lucide-circle-arrow-up' }, { label: 'Done', value: 'done', + description: 'Issues that have been completed', icon: 'i-lucide-circle-check' }, { label: 'Canceled', value: 'canceled', + description: 'Issues that have been canceled or rejected', icon: 'i-lucide-circle-x' }] satisfies SelectMenuItem[] diff --git a/playground/app/pages/components/select.vue b/playground/app/pages/components/select.vue index 0e6c97b1ed..84faf8770b 100644 --- a/playground/app/pages/components/select.vue +++ b/playground/app/pages/components/select.vue @@ -16,22 +16,27 @@ const selectedItems = ref([fruits[0]!, vegetables[0]!]) const statuses = [{ label: 'Backlog', value: 'backlog', + description: 'Issues that have been identified but not yet prioritized', icon: 'i-lucide-circle-help' }, { label: 'Todo', value: 'todo', + description: 'Issues that are ready to be worked on', icon: 'i-lucide-circle-plus' }, { label: 'In Progress', value: 'in_progress', + description: 'Issues that are currently being worked on', icon: 'i-lucide-circle-arrow-up' }, { label: 'Done', value: 'done', + description: 'Issues that have been completed', icon: 'i-lucide-circle-check' }, { label: 'Canceled', value: 'canceled', + description: 'Issues that have been canceled or rejected', icon: 'i-lucide-circle-x' }] satisfies SelectItem[] diff --git a/src/runtime/components/CommandPalette.vue b/src/runtime/components/CommandPalette.vue index 73c7a23c09..9db16c0ce6 100644 --- a/src/runtime/components/CommandPalette.vue +++ b/src/runtime/components/CommandPalette.vue @@ -15,6 +15,7 @@ export interface CommandPaletteItem extends Omit + ui?: Pick [key: string]: any } @@ -135,6 +136,11 @@ export interface CommandPaletteProps = CommandP * @defaultValue 'label' */ labelKey?: string + /** + * The key used to get the description from the item. + * @defaultValue 'description' + */ + descriptionKey?: string class?: any ui?: CommandPalette['slots'] } @@ -153,6 +159,7 @@ export type CommandPaletteSlots = CommandPalett 'item': SlotProps 'item-leading': SlotProps 'item-label': SlotProps + 'item-description': SlotProps 'item-trailing': SlotProps } & Record> & Record> @@ -182,6 +189,7 @@ import UKbd from './Kbd.vue' const props = withDefaults(defineProps>(), { modelValue: '', labelKey: 'label', + descriptionKey: 'description', autofocus: true, back: true }) @@ -403,15 +411,22 @@ function onSelect(e: Event, item: T) { /> - - - {{ item.prefix }} - - - - - - +
+ + + {{ item.prefix }} + + + + + + +
+ + {{ get(item, props.descriptionKey as string) }} + +
+
diff --git a/src/runtime/components/ContextMenu.vue b/src/runtime/components/ContextMenu.vue index b9421b51eb..192448eba3 100644 --- a/src/runtime/components/ContextMenu.vue +++ b/src/runtime/components/ContextMenu.vue @@ -10,6 +10,7 @@ type ContextMenu = ComponentConfig export interface ContextMenuItem extends Omit { label?: string + description?: string /** * @IconifyIcon */ @@ -33,7 +34,7 @@ export interface ContextMenuItem extends Omit + ui?: Pick [key: string]: any } @@ -74,6 +75,11 @@ export interface ContextMenuProps = Arr * @defaultValue 'label' */ labelKey?: keyof NestedItem + /** + * The key used to get the description from the item. + * @defaultValue 'description' + */ + descriptionKey?: keyof NestedItem disabled?: boolean class?: any ui?: ContextMenu['slots'] @@ -91,6 +97,7 @@ export type ContextMenuSlots< 'item': SlotProps 'item-leading': SlotProps 'item-label': SlotProps + 'item-description': SlotProps 'item-trailing': SlotProps 'content-top': (props?: {}) => any 'content-bottom': (props?: {}) => any @@ -111,7 +118,8 @@ const props = withDefaults(defineProps>(), { portal: true, modal: true, externalIcon: true, - labelKey: 'label' + labelKey: 'label', + descriptionKey: 'description' }) const emits = defineEmits() const slots = defineSlots>() @@ -141,6 +149,7 @@ const ui = computed(() => tv({ extend: tv(theme), ...(appConfig.ui?.contextMenu :items="items" :portal="portal" :label-key="(labelKey as keyof NestedItem)" + :description-key="(descriptionKey as keyof NestedItem)" :checked-icon="checkedIcon" :loading-icon="loadingIcon" :external-icon="externalIcon" diff --git a/src/runtime/components/ContextMenuContent.vue b/src/runtime/components/ContextMenuContent.vue index b6abc5e99e..88483459ba 100644 --- a/src/runtime/components/ContextMenuContent.vue +++ b/src/runtime/components/ContextMenuContent.vue @@ -12,6 +12,7 @@ interface ContextMenuContentProps> exte portal?: boolean | string | HTMLElement sub?: boolean labelKey: keyof NestedItem + descriptionKey: keyof NestedItem /** * @IconifyIcon */ @@ -57,7 +58,7 @@ const { dir } = useLocale() const appConfig = useAppConfig() const portalProps = usePortal(toRef(() => props.portal)) -const contentProps = useForwardPropsEmits(reactiveOmit(props, 'sub', 'items', 'portal', 'labelKey', 'checkedIcon', 'loadingIcon', 'externalIcon', 'class', 'ui', 'uiOverride'), emits) +const contentProps = useForwardPropsEmits(reactiveOmit(props, 'sub', 'items', 'portal', 'labelKey', 'descriptionKey', 'checkedIcon', 'loadingIcon', 'externalIcon', 'class', 'ui', 'uiOverride'), emits) const proxySlots = omit(slots, ['default']) const [DefineItemTemplate, ReuseItemTemplate] = createReusableTemplate<{ item: ContextMenuItem, active?: boolean, index: number }>() @@ -81,13 +82,20 @@ const groups = computed(() => - - - {{ get(item, props.labelKey as string) }} - - - - +
+ + + {{ get(item, props.labelKey as string) }} + + + + +
+ + {{ get(item, props.descriptionKey as string) }} + +
+
@@ -135,6 +143,7 @@ const groups = computed(() => :items="(item.children as T)" :align-offset="-4" :label-key="labelKey" + :description-key="descriptionKey" :checked-icon="checkedIcon" :loading-icon="loadingIcon" :external-icon="externalIcon" diff --git a/src/runtime/components/DropdownMenu.vue b/src/runtime/components/DropdownMenu.vue index 30994143d3..b44cebf0a7 100644 --- a/src/runtime/components/DropdownMenu.vue +++ b/src/runtime/components/DropdownMenu.vue @@ -10,6 +10,7 @@ type DropdownMenu = ComponentConfig export interface DropdownMenuItem extends Omit { label?: string + description?: string /** * @IconifyIcon */ @@ -33,7 +34,7 @@ export interface DropdownMenuItem extends Omit + ui?: Pick [key: string]: any } @@ -82,6 +83,11 @@ export interface DropdownMenuProps = A * @defaultValue 'label' */ labelKey?: keyof NestedItem + /** + * The key used to get the description from the item. + * @defaultValue 'description' + */ + descriptionKey?: keyof NestedItem disabled?: boolean class?: any ui?: DropdownMenu['slots'] @@ -99,6 +105,7 @@ export type DropdownMenuSlots< 'item': SlotProps 'item-leading': SlotProps 'item-label': SlotProps + 'item-description': SlotProps 'item-trailing': SlotProps 'content-top': (props?: {}) => any 'content-bottom': (props?: {}) => any @@ -120,7 +127,8 @@ const props = withDefaults(defineProps>(), { portal: true, modal: true, externalIcon: true, - labelKey: 'label' + labelKey: 'label', + descriptionKey: 'description' }) const emits = defineEmits() const slots = defineSlots>() @@ -151,6 +159,7 @@ const ui = computed(() => tv({ extend: tv(theme), ...(appConfig.ui?.dropdownMenu :items="items" :portal="portal" :label-key="(labelKey as keyof NestedItem)" + :description-key="(descriptionKey as keyof NestedItem)" :checked-icon="checkedIcon" :loading-icon="loadingIcon" :external-icon="externalIcon" diff --git a/src/runtime/components/DropdownMenuContent.vue b/src/runtime/components/DropdownMenuContent.vue index e139d29abc..12bf544dea 100644 --- a/src/runtime/components/DropdownMenuContent.vue +++ b/src/runtime/components/DropdownMenuContent.vue @@ -13,6 +13,7 @@ interface DropdownMenuContentProps> ex portal?: boolean | string | HTMLElement sub?: boolean labelKey: keyof NestedItem + descriptionKey: keyof NestedItem /** * @IconifyIcon */ @@ -35,7 +36,7 @@ interface DropdownMenuContentEmits extends RekaDropdownMenuContentEmits {} type DropdownMenuContentSlots< A extends ArrayOrNested = ArrayOrNested, T extends NestedItem = NestedItem -> = Pick, 'item' | 'item-leading' | 'item-label' | 'item-trailing' | 'content-top' | 'content-bottom'> & { +> = Pick, 'item' | 'item-leading' | 'item-label' | 'item-description' | 'item-trailing' | 'content-top' | 'content-bottom'> & { default(props?: {}): any } & DynamicSlots, 'leading' | 'label' | 'trailing', { active?: boolean, index: number }> @@ -66,7 +67,7 @@ const { dir } = useLocale() const appConfig = useAppConfig() const portalProps = usePortal(toRef(() => props.portal)) -const contentProps = useForwardPropsEmits(reactiveOmit(props, 'sub', 'items', 'portal', 'labelKey', 'checkedIcon', 'loadingIcon', 'externalIcon', 'class', 'ui', 'uiOverride'), emits) +const contentProps = useForwardPropsEmits(reactiveOmit(props, 'sub', 'items', 'portal', 'labelKey', 'descriptionKey', 'checkedIcon', 'loadingIcon', 'externalIcon', 'class', 'ui', 'uiOverride'), emits) const proxySlots = omit(slots, ['default']) const [DefineItemTemplate, ReuseItemTemplate] = createReusableTemplate<{ item: DropdownMenuItem, active?: boolean, index: number }>() @@ -90,13 +91,20 @@ const groups = computed(() => - - - {{ get(item, props.labelKey as string) }} - - - - +
+ + + {{ get(item, props.labelKey as string) }} + + + + +
+ + {{ get(item, props.descriptionKey as string) }} + +
+
@@ -146,6 +154,7 @@ const groups = computed(() => :align-offset="-4" :side-offset="3" :label-key="labelKey" + :description-key="descriptionKey" :checked-icon="checkedIcon" :loading-icon="loadingIcon" :external-icon="externalIcon" diff --git a/src/runtime/components/InputMenu.vue b/src/runtime/components/InputMenu.vue index 61bf14e388..f0b5ae03b8 100644 --- a/src/runtime/components/InputMenu.vue +++ b/src/runtime/components/InputMenu.vue @@ -11,6 +11,7 @@ type InputMenu = ComponentConfig interface _InputMenuItem { label?: string + description?: string /** * @IconifyIcon */ @@ -25,7 +26,7 @@ interface _InputMenuItem { disabled?: boolean onSelect?(e?: Event): void class?: any - ui?: Pick + ui?: Pick [key: string]: any } export type InputMenuItem = _InputMenuItem | AcceptableValue | boolean @@ -99,6 +100,11 @@ export interface InputMenuProps = ArrayOr * @defaultValue 'label' */ labelKey?: keyof NestedItem + /** + * When `items` is an array of objects, select the field to use as the description. + * @defaultValue 'description' + */ + descriptionKey?: keyof NestedItem items?: T /** The value of the InputMenu when initially rendered. Use when you do not need to control the state of the InputMenu. */ defaultValue?: GetModelValue @@ -162,6 +168,7 @@ export interface InputMenuSlots< 'item': SlotProps 'item-leading': SlotProps 'item-label': SlotProps + 'item-description': SlotProps 'item-trailing': SlotProps 'tags-item-text': SlotProps 'tags-item-delete': SlotProps @@ -196,6 +203,7 @@ const props = withDefaults(defineProps>(), { autofocusDelay: 0, portal: true, labelKey: 'label' as never, + descriptionKey: 'description' as never, resetSearchTermOnBlur: true, resetSearchTermOnSelect: true }) @@ -532,11 +540,18 @@ defineExpose({ /> - - - {{ isInputItem(item) ? get(item, props.labelKey as string) : item }} - - +
+ + + {{ isInputItem(item) ? get(item, props.labelKey as string) : item }} + + +
+ + {{ get(item, props.descriptionKey as string) }} + +
+
diff --git a/src/runtime/components/Select.vue b/src/runtime/components/Select.vue index 09268beb98..184a552248 100644 --- a/src/runtime/components/Select.vue +++ b/src/runtime/components/Select.vue @@ -10,6 +10,7 @@ type Select = ComponentConfig interface SelectItemBase { label?: string + description?: string /** * @IconifyIcon */ @@ -25,7 +26,7 @@ interface SelectItemBase { disabled?: boolean onSelect?(e?: Event): void class?: any - ui?: Pick + ui?: Pick [key: string]: any } export type SelectItem = SelectItemBase | AcceptableValue | boolean @@ -83,6 +84,11 @@ export interface SelectProps = ArrayOrNested * @defaultValue 'label' */ labelKey?: keyof NestedItem + /** + * When `items` is an array of objects, select the field to use as the description. + * @defaultValue 'description' + */ + descriptionKey?: keyof NestedItem items?: T /** The value of the Select when initially rendered. Use when you do not need to control the state of the Select. */ defaultValue?: GetModelValue @@ -129,6 +135,7 @@ export interface SelectSlots< 'item': SlotProps 'item-leading': SlotProps 'item-label': SlotProps + 'item-description': SlotProps 'item-trailing': SlotProps 'content-top': (props?: {}) => any 'content-bottom': (props?: {}) => any @@ -156,6 +163,7 @@ defineOptions({ inheritAttrs: false }) const props = withDefaults(defineProps>(), { valueKey: 'value' as never, labelKey: 'label' as never, + descriptionKey: 'description' as never, portal: true, autofocusDelay: 0 }) @@ -324,11 +332,18 @@ defineExpose({ /> - - - {{ isSelectItem(item) ? get(item, props.labelKey as string) : item }} - - +
+ + + {{ isSelectItem(item) ? get(item, props.labelKey as string) : item }} + + +
+ + {{ get(item, props.descriptionKey as string) }} + +
+
diff --git a/src/runtime/components/SelectMenu.vue b/src/runtime/components/SelectMenu.vue index 983a219642..80ba46bd09 100644 --- a/src/runtime/components/SelectMenu.vue +++ b/src/runtime/components/SelectMenu.vue @@ -10,6 +10,7 @@ type SelectMenu = ComponentConfig interface _SelectMenuItem { label?: string + description?: string /** * @IconifyIcon */ @@ -24,7 +25,7 @@ interface _SelectMenuItem { disabled?: boolean onSelect?(e?: Event): void class?: any - ui?: Pick + ui?: Pick [key: string]: any } export type SelectMenuItem = _SelectMenuItem | AcceptableValue | boolean @@ -91,6 +92,11 @@ export interface SelectMenuProps = Array * @defaultValue 'label' */ labelKey?: keyof NestedItem + /** + * When `items` is an array of objects, select the field to use as the description. + * @defaultValue 'description' + */ + descriptionKey?: keyof NestedItem items?: T /** The value of the SelectMenu when initially rendered. Use when you do not need to control the state of the SelectMenu. */ defaultValue?: GetModelValue @@ -159,6 +165,7 @@ export interface SelectMenuSlots< 'item': SlotProps 'item-leading': SlotProps 'item-label': SlotProps + 'item-description': SlotProps 'item-trailing': SlotProps 'content-top': (props?: {}) => any 'content-bottom': (props?: {}) => any @@ -190,6 +197,7 @@ const props = withDefaults(defineProps>(), { portal: true, searchInput: true, labelKey: 'label' as never, + descriptionKey: 'description' as never, resetSearchTermOnBlur: true, resetSearchTermOnSelect: true, autofocusDelay: 0 @@ -476,11 +484,18 @@ defineExpose({ /> - - - {{ isSelectItem(item) ? get(item, props.labelKey as string) : item }} - - +
+ + + {{ isSelectItem(item) ? get(item, props.labelKey as string) : item }} + + +
+ + {{ get(item, props.descriptionKey as string) }} + +
+
diff --git a/src/theme/command-palette.ts b/src/theme/command-palette.ts index 94f7aca7d9..dccad842c8 100644 --- a/src/theme/command-palette.ts +++ b/src/theme/command-palette.ts @@ -12,7 +12,7 @@ export default (options: Required) => ({ group: 'p-1 isolate', empty: 'py-6 text-center text-sm text-muted', label: 'p-1.5 text-xs font-semibold text-highlighted', - item: 'group relative w-full flex items-center gap-1.5 p-1.5 text-sm select-none outline-none before:absolute before:z-[-1] before:inset-px before:rounded-md data-disabled:cursor-not-allowed data-disabled:opacity-75', + item: 'group relative w-full flex items-start gap-1.5 p-1.5 text-sm select-none outline-none before:absolute before:z-[-1] before:inset-px before:rounded-md data-disabled:cursor-not-allowed data-disabled:opacity-75', itemLeadingIcon: 'shrink-0 size-5', itemLeadingAvatar: 'shrink-0', itemLeadingAvatarSize: '2xs', @@ -23,10 +23,12 @@ export default (options: Required) => ({ itemTrailingHighlightedIcon: 'shrink-0 size-5 text-dimmed hidden group-data-highlighted:inline-flex', itemTrailingKbds: 'hidden lg:inline-flex items-center shrink-0 gap-0.5', itemTrailingKbdsSize: 'md', - itemLabel: 'truncate space-x-1 text-dimmed', + itemLabel: 'truncate space-x-1 text-dimmed text-left', itemLabelBase: 'text-highlighted [&>mark]:text-inverted [&>mark]:bg-primary', itemLabelPrefix: 'text-default', - itemLabelSuffix: 'text-dimmed [&>mark]:text-inverted [&>mark]:bg-primary' + itemLabelSuffix: 'text-dimmed [&>mark]:text-inverted [&>mark]:bg-primary', + itemDescription: 'text-muted text-xs truncate text-left', + itemContent: 'flex-1 min-w-0 flex flex-col text-left' }, variants: { active: { diff --git a/src/theme/context-menu.ts b/src/theme/context-menu.ts index 9a49e6b7e2..a73ce9dd0e 100644 --- a/src/theme/context-menu.ts +++ b/src/theme/context-menu.ts @@ -7,7 +7,7 @@ export default (options: Required) => ({ group: 'p-1 isolate', label: 'w-full flex items-center font-semibold text-highlighted', separator: '-mx-1 my-1 h-px bg-border', - item: 'group relative w-full flex items-center select-none outline-none before:absolute before:z-[-1] before:inset-px before:rounded-md data-disabled:cursor-not-allowed data-disabled:opacity-75', + item: 'group relative w-full flex items-start select-none outline-none before:absolute before:z-[-1] before:inset-px before:rounded-md data-disabled:cursor-not-allowed data-disabled:opacity-75', itemLeadingIcon: 'shrink-0', itemLeadingAvatar: 'shrink-0', itemLeadingAvatarSize: '', @@ -15,8 +15,10 @@ export default (options: Required) => ({ itemTrailingIcon: 'shrink-0', itemTrailingKbds: 'hidden lg:inline-flex items-center shrink-0', itemTrailingKbdsSize: '', - itemLabel: 'truncate', - itemLabelExternalIcon: 'inline-block size-3 align-top text-dimmed' + itemLabel: 'truncate text-left', + itemLabelExternalIcon: 'inline-block size-3 align-top text-dimmed', + itemDescription: 'text-muted text-xs truncate text-left', + itemContent: 'flex-1 min-w-0 flex flex-col text-left' }, variants: { color: { diff --git a/src/theme/dropdown-menu.ts b/src/theme/dropdown-menu.ts index c47b132fac..a4ef848a27 100644 --- a/src/theme/dropdown-menu.ts +++ b/src/theme/dropdown-menu.ts @@ -8,7 +8,7 @@ export default (options: Required) => ({ group: 'p-1 isolate', label: 'w-full flex items-center font-semibold text-highlighted', separator: '-mx-1 my-1 h-px bg-border', - item: 'group relative w-full flex items-center select-none outline-none before:absolute before:z-[-1] before:inset-px before:rounded-md data-disabled:cursor-not-allowed data-disabled:opacity-75', + item: 'group relative w-full flex items-start select-none outline-none before:absolute before:z-[-1] before:inset-px before:rounded-md data-disabled:cursor-not-allowed data-disabled:opacity-75', itemLeadingIcon: 'shrink-0', itemLeadingAvatar: 'shrink-0', itemLeadingAvatarSize: '', @@ -16,8 +16,10 @@ export default (options: Required) => ({ itemTrailingIcon: 'shrink-0', itemTrailingKbds: 'hidden lg:inline-flex items-center shrink-0', itemTrailingKbdsSize: '', - itemLabel: 'truncate', - itemLabelExternalIcon: 'inline-block size-3 align-top text-dimmed' + itemLabel: 'truncate text-left', + itemLabelExternalIcon: 'inline-block size-3 align-top text-dimmed', + itemDescription: 'text-muted text-xs truncate text-left', + itemContent: 'flex-1 min-w-0 flex flex-col text-left' }, variants: { color: { diff --git a/src/theme/input-menu.ts b/src/theme/input-menu.ts index 1ce24a70c5..cf13b2e211 100644 --- a/src/theme/input-menu.ts +++ b/src/theme/input-menu.ts @@ -14,7 +14,7 @@ export default (options: Required) => { empty: 'text-center text-muted', label: 'font-semibold text-highlighted', separator: '-mx-1 my-1 h-px bg-border', - item: ['group relative w-full flex items-center gap-1.5 p-1.5 text-sm select-none outline-none before:absolute before:z-[-1] before:inset-px before:rounded-md data-disabled:cursor-not-allowed data-disabled:opacity-75 text-default data-highlighted:not-data-disabled:text-highlighted data-highlighted:not-data-disabled:before:bg-elevated/50', options.theme.transitions && 'transition-colors before:transition-colors'], + item: ['group relative w-full flex items-start gap-1.5 p-1.5 text-sm select-none outline-none before:absolute before:z-[-1] before:inset-px before:rounded-md data-disabled:cursor-not-allowed data-disabled:opacity-75 text-default data-highlighted:not-data-disabled:text-highlighted data-highlighted:not-data-disabled:before:bg-elevated/50', options.theme.transitions && 'transition-colors before:transition-colors'], itemLeadingIcon: ['shrink-0 text-dimmed group-data-highlighted:not-group-data-disabled:text-default', options.theme.transitions && 'transition-colors'], itemLeadingAvatar: 'shrink-0', itemLeadingAvatarSize: '', @@ -22,7 +22,9 @@ export default (options: Required) => { itemLeadingChipSize: '', itemTrailing: 'ms-auto inline-flex gap-1.5 items-center', itemTrailingIcon: 'shrink-0', - itemLabel: 'truncate', + itemLabel: 'truncate text-left', + itemDescription: 'text-muted text-xs truncate text-left', + itemContent: 'flex-1 min-w-0 flex flex-col text-left', tagsItem: 'px-1.5 py-0.5 rounded-sm font-medium inline-flex items-center gap-0.5 ring ring-inset ring-accented bg-elevated text-default data-disabled:cursor-not-allowed data-disabled:opacity-75', tagsItemText: 'truncate', tagsItemDelete: ['inline-flex items-center rounded-xs text-dimmed hover:text-default hover:bg-accented/75 disabled:pointer-events-none', options.theme.transitions && 'transition-colors'], diff --git a/src/theme/select.ts b/src/theme/select.ts index 8980bb4041..1e76c810b7 100644 --- a/src/theme/select.ts +++ b/src/theme/select.ts @@ -17,7 +17,7 @@ export default (options: Required) => { empty: 'text-center text-muted', label: 'font-semibold text-highlighted', separator: '-mx-1 my-1 h-px bg-border', - item: ['group relative w-full flex items-center select-none outline-none before:absolute before:z-[-1] before:inset-px before:rounded-md data-disabled:cursor-not-allowed data-disabled:opacity-75 text-default data-highlighted:not-data-disabled:text-highlighted data-highlighted:not-data-disabled:before:bg-elevated/50', options.theme.transitions && 'transition-colors before:transition-colors'], + item: ['group relative w-full flex items-start select-none outline-none before:absolute before:z-[-1] before:inset-px before:rounded-md data-disabled:cursor-not-allowed data-disabled:opacity-75 text-default data-highlighted:not-data-disabled:text-highlighted data-highlighted:not-data-disabled:before:bg-elevated/50', options.theme.transitions && 'transition-colors before:transition-colors'], itemLeadingIcon: ['shrink-0 text-dimmed group-data-highlighted:not-group-data-disabled:text-default', options.theme.transitions && 'transition-colors'], itemLeadingAvatar: 'shrink-0', itemLeadingAvatarSize: '', @@ -25,7 +25,9 @@ export default (options: Required) => { itemLeadingChipSize: '', itemTrailing: 'ms-auto inline-flex gap-1.5 items-center', itemTrailingIcon: 'shrink-0', - itemLabel: 'truncate' + itemLabel: 'truncate text-left', + itemDescription: 'text-muted text-xs truncate text-left', + itemContent: 'flex-1 min-w-0 flex flex-col text-left' }, variants: { ...buttonGroupVariant, diff --git a/test/components/CommandPalette.spec.ts b/test/components/CommandPalette.spec.ts index 97d41123af..5465a6a210 100644 --- a/test/components/CommandPalette.spec.ts +++ b/test/components/CommandPalette.spec.ts @@ -62,11 +62,30 @@ describe('CommandPalette', () => { }] }] + const groupsWithDescription = [{ + id: 'actions', + items: [{ + label: 'Add new file', + description: 'Create a new file', + suffix: 'Create a new file in the current directory or workspace.', + icon: 'i-lucide-file-plus', + kbds: ['meta', 'N'], + active: true + }, { + label: 'Add new folder', + description: 'Create a new folder', + suffix: 'Create a new folder in the current directory or workspace.', + icon: 'i-lucide-folder-plus', + kbds: ['meta', 'F'] + }] + }] + const props = { groups } it.each([ // Props ['with groups', { props }], + ['with groups with description', { props: { groups: groupsWithDescription } }], ['without data', {}], ['with modelValue', { props: { ...props, modelValue: groups[2]?.items[0] } }], ['with defaultValue', { props: { ...props, defaultValue: groups[2]?.items[0] } }], @@ -88,6 +107,7 @@ describe('CommandPalette', () => { ['with item-leading slot', { props, slots: { 'item-leading': () => 'Item leading slot' } }], ['with item-label slot', { props, slots: { 'item-label': () => 'Item label slot' } }], ['with item-trailing slot', { props, slots: { 'item-trailing': () => 'Item trailing slot' } }], + ['with item-description slot', { props, slots: { 'item-description': () => 'Item description slot' } }], ['with custom slot', { props, slots: { custom: () => 'Custom slot' } }], ['with close slot', { props: { ...props, close: true }, slots: { close: () => 'Close slot' } }], ['with footer slot', { props, slots: { footer: () => 'Footer slot' } }] diff --git a/test/components/ContextMenu.spec.ts b/test/components/ContextMenu.spec.ts index b19aa6f2ba..7949766523 100644 --- a/test/components/ContextMenu.spec.ts +++ b/test/components/ContextMenu.spec.ts @@ -78,11 +78,37 @@ describe('ContextMenu', () => { }] ] + const itemsWithDescription = [ + [{ + label: 'Appearance', + description: 'Change theme', + children: [{ + label: 'System', + description: 'Use system setting', + icon: 'i-lucide-monitor' + }, { + label: 'Light', + description: 'Light theme', + icon: 'i-lucide-sun' + }] + }], [{ + label: 'Show Sidebar', + description: 'Toggle sidebar', + color: 'primary', + kbds: ['meta', 'S'] + }, { + label: 'Show Toolbar', + description: 'Toggle toolbar', + kbds: ['shift', 'meta', 'D'] + }] + ] + const props = { portal: false, items } it.each([ // Props ['with items', { props }], + ['with items with description', { props: { ...props, items: itemsWithDescription } }], ['with labelKey', { props: { ...props, labelKey: 'icon' } }], ['with disabled', { props: { ...props, disabled: true } }], ...sizes.map((size: string) => [`with size ${size}`, { props: { ...props, size } }]), @@ -96,6 +122,7 @@ describe('ContextMenu', () => { ['with item-leading slot', { props, slots: { 'item-leading': () => 'Item leading slot' } }], ['with item-label slot', { props, slots: { 'item-label': () => 'Item label slot' } }], ['with item-trailing slot', { props, slots: { 'item-trailing': () => 'Item trailing slot' } }], + ['with item-description slot', { props, slots: { 'item-description': () => 'Item description slot' } }], ['with custom slot', { props, slots: { custom: () => 'Custom slot' } }] ])('renders %s correctly', async (nameOrHtml: string, options: { props?: ContextMenuProps, slots?: Partial }) => { const wrapper = await mountSuspended(ContextMenuWrapper, options as any) diff --git a/test/components/DropdownMenu.spec.ts b/test/components/DropdownMenu.spec.ts index 59b5807ccc..da87c57731 100644 --- a/test/components/DropdownMenu.spec.ts +++ b/test/components/DropdownMenu.spec.ts @@ -87,11 +87,34 @@ describe('DropdownMenu', () => { }] ] + const itemsWithDescription = [ + [{ + label: 'My account', + description: 'Account settings', + avatar: { + src: 'https://github.com/benjamincanac.png' + }, + type: 'label' + }], + [{ + label: 'Profile', + description: 'View your profile', + icon: 'i-lucide-user', + slot: 'custom' + }, { + label: 'Billing', + description: 'Manage billing', + icon: 'i-lucide-credit-card', + kbds: ['meta', 'b'] + }] + ] + const props = { open: true, portal: false, items } it.each([ // Props ['with items', { props }], + ['with items with description', { props: { ...props, items: itemsWithDescription } }], ['with labelKey', { props: { ...props, labelKey: 'icon' } }], ['with disabled', { props: { ...props, disabled: true } }], ['with arrow', { props: { ...props, arrow: true } }], @@ -106,6 +129,7 @@ describe('DropdownMenu', () => { ['with item-leading slot', { props, slots: { 'item-leading': () => 'Item leading slot' } }], ['with item-label slot', { props, slots: { 'item-label': () => 'Item label slot' } }], ['with item-trailing slot', { props, slots: { 'item-trailing': () => 'Item trailing slot' } }], + ['with item-description slot', { props, slots: { 'item-description': () => 'Item description slot' } }], ['with custom slot', { props, slots: { custom: () => 'Custom slot' } }] ])('renders %s correctly', async (nameOrHtml: string, options: { props?: DropdownMenuProps, slots?: Partial }) => { const html = await ComponentRender(nameOrHtml, options, DropdownMenu) diff --git a/test/components/InputMenu.spec.ts b/test/components/InputMenu.spec.ts index 904bcc8d91..d4a05c115e 100644 --- a/test/components/InputMenu.spec.ts +++ b/test/components/InputMenu.spec.ts @@ -34,11 +34,16 @@ describe('InputMenu', () => { icon: 'i-lucide-circle-x' }] + const itemsWithDescription = [ + ...items.map(item => ({ ...item, description: 'Description' })) + ] + const props = { open: true, portal: false, items } it.each([ // Props ['with items', { props }], + ['with items with description', { props: { ...props, items: itemsWithDescription } }], ['with modelValue', { props: { ...props, modelValue: items[0] } }], ['with defaultValue', { props: { ...props, defaultValue: items[0] } }], ['with valueKey', { props: { ...props, valueKey: 'value' } }], @@ -81,6 +86,7 @@ describe('InputMenu', () => { ['with item-leading slot', { props, slots: { 'item-leading': () => 'Item leading slot' } }], ['with item-label slot', { props, slots: { 'item-label': () => 'Item label slot' } }], ['with item-trailing slot', { props, slots: { 'item-trailing': () => 'Item trailing slot' } }], + ['with item-description slot', { props, slots: { 'item-description': () => 'Item description slot' } }], ['with create-item-label slot', { props: { ...props, searchTerm: 'New value', createItem: true }, slots: { 'create-item-label': () => 'Create item slot' } }] ])('renders %s correctly', async (nameOrHtml: string, options: { props?: InputMenuProps, slots?: Partial }) => { const html = await ComponentRender(nameOrHtml, options, InputMenu) diff --git a/test/components/Select.spec.ts b/test/components/Select.spec.ts index 50325fddf4..f1bfa30402 100644 --- a/test/components/Select.spec.ts +++ b/test/components/Select.spec.ts @@ -34,11 +34,16 @@ describe('Select', () => { icon: 'i-lucide-circle-x' }] + const itemsWithDescription = [ + ...items.map(item => ({ ...item, description: 'Description' })) + ] + const props = { open: true, portal: false, items } it.each([ // Props ['with items', { props }], + ['with items with description', { props: { ...props, items: itemsWithDescription } }], ['with modelValue', { props: { ...props, modelValue: items[0] } }], ['with defaultValue', { props: { ...props, defaultValue: items[0] } }], ['with valueKey', { props: { ...props, valueKey: 'label' } }], diff --git a/test/components/SelectMenu.spec.ts b/test/components/SelectMenu.spec.ts index 5ab2601b2a..933ff7f1b0 100644 --- a/test/components/SelectMenu.spec.ts +++ b/test/components/SelectMenu.spec.ts @@ -34,11 +34,16 @@ describe('SelectMenu', () => { icon: 'i-lucide-circle-x' }] + const itemsWithDescription = [ + ...items.map(item => ({ ...item, description: 'Description' })) + ] + const props = { open: true, portal: false, items } it.each([ // Props ['with items', { props }], + ['with items with description', { props: { ...props, items: itemsWithDescription } }], ['with modelValue', { props: { ...props, modelValue: items[0] } }], ['with defaultValue', { props: { ...props, defaultValue: items[0] } }], ['with valueKey', { props: { ...props, valueKey: 'value' } }], diff --git a/test/components/__snapshots__/CommandPalette-vue.spec.ts.snap b/test/components/__snapshots__/CommandPalette-vue.spec.ts.snap index ad2188c025..c07ea58503 100644 --- a/test/components/__snapshots__/CommandPalette-vue.spec.ts.snap +++ b/test/components/__snapshots__/CommandPalette-vue.spec.ts.snap @@ -8,27 +8,52 @@ exports[`CommandPalette > renders with as correctly 1`] = `
@@ -45,27 +70,52 @@ exports[`CommandPalette > renders with class correctly 1`] = `
@@ -80,27 +130,52 @@ exports[`CommandPalette > renders with close correctly 1`] = `
@@ -115,27 +190,52 @@ exports[`CommandPalette > renders with close slot correctly 1`] = `
@@ -150,27 +250,52 @@ exports[`CommandPalette > renders with closeIcon correctly 1`] = `
@@ -187,25 +312,47 @@ exports[`CommandPalette > renders with custom slot correctly 1`] = `
@@ -222,27 +369,52 @@ exports[`CommandPalette > renders with defaultValue correctly 1`] = `
@@ -259,27 +431,52 @@ exports[`CommandPalette > renders with disabled correctly 1`] = `
@@ -296,27 +493,52 @@ exports[`CommandPalette > renders with empty slot correctly 1`] = `
@@ -333,27 +555,52 @@ exports[`CommandPalette > renders with footer slot correctly 1`] = `
@@ -370,27 +617,52 @@ exports[`CommandPalette > renders with groups correctly 1`] = `
@@ -399,7 +671,7 @@ exports[`CommandPalette > renders with groups correctly 1`] = ` " `; -exports[`CommandPalette > renders with icon correctly 1`] = ` +exports[`CommandPalette > renders with groups with description correctly 1`] = ` "
@@ -407,27 +679,79 @@ exports[`CommandPalette > renders with icon correctly 1`] = `
+
+ + +
" +`; + +exports[`CommandPalette > renders with icon correctly 1`] = ` +"
+
+ +
+
+
@@ -444,15 +768,18 @@ exports[`CommandPalette > renders with item slot correctly 1`] = `
@@ -461,7 +788,7 @@ exports[`CommandPalette > renders with item slot correctly 1`] = `
" `; -exports[`CommandPalette > renders with item-label slot correctly 1`] = ` +exports[`CommandPalette > renders with item-description slot correctly 1`] = ` "
@@ -469,27 +796,114 @@ exports[`CommandPalette > renders with item-label slot correctly 1`] = `
+
+ + +
" +`; + +exports[`CommandPalette > renders with item-label slot correctly 1`] = ` +"
+
+ +
+
+
@@ -506,21 +920,38 @@ exports[`CommandPalette > renders with item-leading slot correctly 1`] = `
@@ -537,21 +968,49 @@ exports[`CommandPalette > renders with item-trailing slot correctly 1`] = `
@@ -568,31 +1027,55 @@ exports[`CommandPalette > renders with labelKey correctly 1`] = `
@@ -610,27 +1093,52 @@ exports[`CommandPalette > renders with loading correctly 1`] = `
@@ -647,27 +1155,52 @@ exports[`CommandPalette > renders with loadingIcon correctly 1`] = `
@@ -684,27 +1217,52 @@ exports[`CommandPalette > renders with modelValue correctly 1`] = `
@@ -721,27 +1279,52 @@ exports[`CommandPalette > renders with placeholder correctly 1`] = `
@@ -758,27 +1341,52 @@ exports[`CommandPalette > renders with selectedIcon correctly 1`] = `
@@ -795,27 +1403,52 @@ exports[`CommandPalette > renders with ui correctly 1`] = `
diff --git a/test/components/__snapshots__/CommandPalette.spec.ts.snap b/test/components/__snapshots__/CommandPalette.spec.ts.snap index 3f80fc55f0..796e344c57 100644 --- a/test/components/__snapshots__/CommandPalette.spec.ts.snap +++ b/test/components/__snapshots__/CommandPalette.spec.ts.snap @@ -8,27 +8,52 @@ exports[`CommandPalette > renders with as correctly 1`] = `
@@ -45,27 +70,52 @@ exports[`CommandPalette > renders with class correctly 1`] = `
@@ -83,27 +133,52 @@ exports[`CommandPalette > renders with close correctly 1`] = `
@@ -118,27 +193,52 @@ exports[`CommandPalette > renders with close slot correctly 1`] = `
@@ -156,27 +256,52 @@ exports[`CommandPalette > renders with closeIcon correctly 1`] = `
@@ -193,25 +318,47 @@ exports[`CommandPalette > renders with custom slot correctly 1`] = `
@@ -228,27 +375,52 @@ exports[`CommandPalette > renders with defaultValue correctly 1`] = `
@@ -265,27 +437,52 @@ exports[`CommandPalette > renders with disabled correctly 1`] = `
@@ -302,27 +499,52 @@ exports[`CommandPalette > renders with empty slot correctly 1`] = `
@@ -339,27 +561,52 @@ exports[`CommandPalette > renders with footer slot correctly 1`] = `
@@ -376,27 +623,52 @@ exports[`CommandPalette > renders with groups correctly 1`] = `
@@ -405,35 +677,87 @@ exports[`CommandPalette > renders with groups correctly 1`] = `
" `; -exports[`CommandPalette > renders with icon correctly 1`] = ` +exports[`CommandPalette > renders with groups with description correctly 1`] = ` "
-
+
+
+ + +
" +`; + +exports[`CommandPalette > renders with icon correctly 1`] = ` +"
+
+ +
+
+
@@ -450,15 +774,18 @@ exports[`CommandPalette > renders with item slot correctly 1`] = `
@@ -467,7 +794,7 @@ exports[`CommandPalette > renders with item slot correctly 1`] = `
" `; -exports[`CommandPalette > renders with item-label slot correctly 1`] = ` +exports[`CommandPalette > renders with item-description slot correctly 1`] = ` "
@@ -475,27 +802,114 @@ exports[`CommandPalette > renders with item-label slot correctly 1`] = `
+
+ + +
" +`; + +exports[`CommandPalette > renders with item-label slot correctly 1`] = ` +"
+
+ +
+
+
@@ -512,21 +926,38 @@ exports[`CommandPalette > renders with item-leading slot correctly 1`] = `
@@ -543,21 +974,49 @@ exports[`CommandPalette > renders with item-trailing slot correctly 1`] = `
@@ -574,31 +1033,55 @@ exports[`CommandPalette > renders with labelKey correctly 1`] = `
@@ -616,27 +1099,52 @@ exports[`CommandPalette > renders with loading correctly 1`] = `
@@ -653,27 +1161,52 @@ exports[`CommandPalette > renders with loadingIcon correctly 1`] = `
@@ -690,27 +1223,52 @@ exports[`CommandPalette > renders with modelValue correctly 1`] = `
@@ -727,27 +1285,52 @@ exports[`CommandPalette > renders with placeholder correctly 1`] = `
@@ -764,27 +1347,52 @@ exports[`CommandPalette > renders with selectedIcon correctly 1`] = `
@@ -801,27 +1409,52 @@ exports[`CommandPalette > renders with ui correctly 1`] = `
diff --git a/test/components/__snapshots__/ContextMenu-vue.spec.ts.snap b/test/components/__snapshots__/ContextMenu-vue.spec.ts.snap index bdf9da53f3..ec8c78d8d0 100644 --- a/test/components/__snapshots__/ContextMenu-vue.spec.ts.snap +++ b/test/components/__snapshots__/ContextMenu-vue.spec.ts.snap @@ -9,39 +9,67 @@ exports[`ContextMenu > renders with class correctly 1`] = `
@@ -60,39 +88,67 @@ exports[`ContextMenu > renders with custom slot correctly 1`] = `
@@ -111,39 +167,67 @@ exports[`ContextMenu > renders with default slot correctly 1`] = `
@@ -175,39 +259,67 @@ exports[`ContextMenu > renders with externalIcon correctly 1`] = `
@@ -226,21 +338,100 @@ exports[`ContextMenu > renders with item slot correctly 1`] = `