diff --git a/packages/api-generator/src/locale/en/v-list-item.json b/packages/api-generator/src/locale/en/v-list-item.json new file mode 100644 index 00000000000..5354518a01d --- /dev/null +++ b/packages/api-generator/src/locale/en/v-list-item.json @@ -0,0 +1,17 @@ +{ + "props": { + "active": "Controls the **active** state of the item. This is typically used to highlight the component", + "color": "Applies specified color to the control when in an **active** state or **input-value** is **true** - supports utility colors (for example `success` or `purple`) or css color (for example `success` or `purple`) or css color (`#033` or `rgba(255, 0, 0, 0.5)`). Find a list of built-in classes on the [colors page](/styles/colors#material-colors)", + "contained": "Changes the component style by changing how color is applied to the background.", + "title": "Generates a `v-list-item-title` component with the supplied value", + "value": "The value used for selection.", + "nav": "MISSING DESCRIPTION ([edit in github](https://github.com/vuetifyjs/vuetify/tree/master/packages/api-generator/src/locale/en/v-list-item.json))", + "lines": "MISSING DESCRIPTION ([edit in github](https://github.com/vuetifyjs/vuetify/tree/master/packages/api-generator/src/locale/en/v-list-item.json))" + }, + "events": { + "click": "MISSING DESCRIPTION ([edit in github](https://github.com/vuetifyjs/vuetify/tree/master/packages/api-generator/src/locale/en/v-list-item.json))", + "clickOnce": "MISSING DESCRIPTION ([edit in github](https://github.com/vuetifyjs/vuetify/tree/master/packages/api-generator/src/locale/en/v-list-item.json))", + "click:append": "Emitted when appended icon or avatar is clicked.", + "click:prepend": "Emitted when prepended icon or avatar is clicked." + } +} diff --git a/packages/vuetify/src/components/VList/ListAvatar.tsx b/packages/vuetify/src/components/VList/ListAvatar.tsx new file mode 100644 index 00000000000..00003bd7149 --- /dev/null +++ b/packages/vuetify/src/components/VList/ListAvatar.tsx @@ -0,0 +1,58 @@ +// Components +import { VAvatar } from '@/components/VAvatar' + +// Composables +import { useLocale } from '@/composables/locale' + +// Utilities +import { callEvent } from '@/util' + +// Types +import type { EventProp } from '@/util' + +type names = 'prepend' | 'append' + +type ListAvatarProps = { + title: string | number | undefined +} & { + [K in `${T}Avatar`]: string | undefined +} & { + [K in `onClick:${T}`]: EventProp | undefined +} + +type Listeners = U extends `onClick:${infer V extends names}` ? V : never + +export function useListAvatar> (props: T & ListAvatarProps) { + const { t } = useLocale() + + function ListAvatar ({ name }: { name: Extract }) { + const localeKey = { + prepend: 'prependAction', + append: 'appendAction', + }[name] + const listener = props[`onClick:${name}`] as EventProp | undefined + + function onKeydown (e: KeyboardEvent) { + if (e.key !== 'Enter' && e.key !== ' ') return + + e.preventDefault() + e.stopPropagation() + callEvent(listener, new PointerEvent('click', e)) + } + + const label = listener && localeKey + ? t(`$vuetify.list.${localeKey}`, props.title ?? '') + : undefined + + return ( + + ) + } + + return { ListAvatar } +} diff --git a/packages/vuetify/src/components/VList/ListIcon.tsx b/packages/vuetify/src/components/VList/ListIcon.tsx new file mode 100644 index 00000000000..ac3aeca2607 --- /dev/null +++ b/packages/vuetify/src/components/VList/ListIcon.tsx @@ -0,0 +1,59 @@ +// Components +import { VIcon } from '@/components/VIcon' + +// Composables +import { useLocale } from '@/composables/locale' + +// Utilities +import { callEvent } from '@/util' + +// Types +import type { IconValue } from '@/composables/icons' +import type { EventProp } from '@/util' + +type names = 'prepend' | 'append' + +type ListIconProps = { + title: string | number | undefined +} & { + [K in `${T}Icon`]: IconValue | undefined +} & { + [K in `onClick:${T}`]: EventProp | undefined +} + +type Listeners = U extends `onClick:${infer V extends names}` ? V : never + +export function useListIcon> (props: T & ListIconProps) { + const { t } = useLocale() + + function ListIcon ({ name }: { name: Extract }) { + const localeKey = { + prepend: 'prependAction', + append: 'appendAction', + }[name] + const listener = props[`onClick:${name}`] as EventProp | undefined + + function onKeydown (e: KeyboardEvent) { + if (e.key !== 'Enter' && e.key !== ' ') return + + e.preventDefault() + e.stopPropagation() + callEvent(listener, new PointerEvent('click', e)) + } + + const label = listener && localeKey + ? t(`$vuetify.list.${localeKey}`, props.title ?? '') + : undefined + + return ( + + ) + } + + return { ListIcon } +} diff --git a/packages/vuetify/src/components/VList/VListItem.tsx b/packages/vuetify/src/components/VList/VListItem.tsx index 12e58e90ff0..30cf9307b01 100644 --- a/packages/vuetify/src/components/VList/VListItem.tsx +++ b/packages/vuetify/src/components/VList/VListItem.tsx @@ -2,11 +2,11 @@ import './VListItem.sass' // Components +import { useListAvatar } from './ListAvatar' +import { useListIcon } from './ListIcon' import { VListItemSubtitle } from './VListItemSubtitle' import { VListItemTitle } from './VListItemTitle' -import { VAvatar } from '@/components/VAvatar' import { VDefaultsProvider } from '@/components/VDefaultsProvider' -import { VIcon } from '@/components/VIcon' // Composables import { useList } from './list' @@ -90,6 +90,9 @@ export const makeVListItemProps = propsFactory({ onClick: EventProp<[MouseEvent | KeyboardEvent]>(), onClickOnce: EventProp<[MouseEvent]>(), + 'onClick:prepend': EventProp<[MouseEvent]>(), + 'onClick:append': EventProp<[MouseEvent]>(), + ...makeBorderProps(), ...makeComponentProps(), ...makeDensityProps(), @@ -111,6 +114,8 @@ export const VListItem = genericComponent()({ emits: { click: (e: MouseEvent | KeyboardEvent) => true, + 'click:prepend': (e: MouseEvent | KeyboardEvent) => true, + 'click:append': (e: MouseEvent | KeyboardEvent) => true, }, setup (props, { attrs, slots, emit }) { @@ -172,6 +177,8 @@ export const VListItem = genericComponent()({ const { dimensionStyles } = useDimension(props) const { elevationClasses } = useElevation(props) const { roundedClasses } = useRounded(roundedProps) + const { ListAvatar } = useListAvatar(props) + const { ListIcon } = useListIcon(props) const lineClasses = computed(() => props.lines ? `v-list-item--${props.lines}-line` : undefined) const slotProps = computed(() => ({ @@ -272,18 +279,16 @@ export const VListItem = genericComponent()({ { !slots.prepend ? ( <> { props.prependAvatar && ( - )} { props.prependIcon && ( - )} @@ -334,18 +339,16 @@ export const VListItem = genericComponent()({ { !slots.append ? ( <> { props.appendIcon && ( - )} { props.appendAvatar && ( - )} diff --git a/packages/vuetify/src/locale/en.ts b/packages/vuetify/src/locale/en.ts index 0c59dac42d9..32e9bdb7c7a 100644 --- a/packages/vuetify/src/locale/en.ts +++ b/packages/vuetify/src/locale/en.ts @@ -104,4 +104,8 @@ export default { loadMore: 'Load more', empty: 'No more', }, + list: { + prependAction: '{0} prepended action', + appendAction: '{0} appended action', + }, }