Skip to content

Commit

Permalink
feat(VDataTable): add mobile view (#19431)
Browse files Browse the repository at this point in the history
Co-authored-by: Chris Hudson <cjhudson101@gmail.com>
Co-authored-by: John Leider <john@vuetifyjs.com>
  • Loading branch information
3 people committed Apr 29, 2024
1 parent 1ee802d commit 66880ce
Show file tree
Hide file tree
Showing 9 changed files with 209 additions and 40 deletions.
1 change: 0 additions & 1 deletion packages/api-generator/src/locale/en/VDataTable.json
Expand Up @@ -23,7 +23,6 @@
"itemClass": "Property on supplied `items` that contains item's row class or function that takes an item as an argument and returns the class of corresponding row.",
"itemsPerPage": "Changes how many items per page should be visible. Can be used with `.sync` modifier. Setting this prop to `-1` will display all items on the page.",
"locale": "Sets the locale used for sorting. This is passed into [`Intl.Collator()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/Collator/Collator) in the default `customSort` function.",
"mobileBreakpoint": "Used to set when to toggle between regular table and mobile view.",
"multiSort": "If `true` then one can sort on multiple properties.",
"mustSort": "If `true` then one can not disable sorting, it will always switch between ascending and descending.",
"page": "The current displayed page number (1-indexed).",
Expand Down
3 changes: 2 additions & 1 deletion packages/api-generator/src/locale/en/display.json
@@ -1,5 +1,6 @@
{
"props": {
"mobileBreakpoint": "Sets the designated mobile breakpoint for the component."
"mobile": "Explicitly designate as a mobile display configuration.",
"mobileBreakpoint": "Overrides the display configuration default."
}
}
35 changes: 34 additions & 1 deletion packages/vuetify/src/components/VDataTable/VDataTable.sass
Expand Up @@ -55,7 +55,7 @@
align-items: center

> th.v-data-table__th--fixed
position: sticky
position: sticky

> th.v-data-table__th--sortable:hover
cursor: pointer
Expand Down Expand Up @@ -126,3 +126,36 @@
.v-data-table-rows-loading,
.v-data-table-rows-no-data
text-align: center

.v-data-table__tr--mobile
> .v-data-table__td--expanded-row
grid-template-columns: 0
justify-content: center

> .v-data-table__td--select-row
grid-template-columns: 0
justify-content: end

> td
align-items: center
column-gap: 4px
display: grid
grid-template-columns: repeat(2, 1fr)
min-height: var(--v-table-row-height)

&:not(:last-child)
border-bottom: 0 !important

.v-data-table__td-title
font-weight: bold
text-align: left

.v-data-table__td-value
text-align: right

.v-data-table__td
&-sort-icon
color: $data-table-header-mobile-chip-icon-color

&-active
color: $data-table-header-mobile-chip-icon-color-active
42 changes: 21 additions & 21 deletions packages/vuetify/src/components/VDataTable/VDataTableFooter.sass
Expand Up @@ -4,33 +4,33 @@

@include tools.layer('components')
.v-data-table-footer
display: flex
align-items: center
display: flex
flex-wrap: wrap
padding: $data-table-footer-padding
justify-content: flex-end
padding: $data-table-footer-padding

.v-data-table-footer__items-per-page
display: flex
align-items: center
justify-content: center
&__items-per-page
align-items: center
display: flex
justify-content: center

> span
padding-inline-end: $data-table-footer-items-per-page-padding
> span
padding-inline-end: $data-table-footer-items-per-page-padding

> .v-select
width: $data-table-footer-select-width
> .v-select
width: $data-table-footer-select-width

.v-data-table-footer__info
display: flex
justify-content: flex-end
min-width: $data-table-footer-info-min-width
padding: $data-table-footer-info-padding
&__info
display: flex
justify-content: flex-end
min-width: $data-table-footer-info-min-width
padding: $data-table-footer-info-padding

.v-data-table-footer__pagination
display: flex
align-items: center
margin-inline-start: $data-table-footer-pagination-margin-inline-start
&__paginationz
align-items: center
display: flex
margin-inline-start: $data-table-footer-pagination-margin-inline-start

.v-data-table-footer__page
padding: 0 8px
&__page
padding: 0 8px
92 changes: 87 additions & 5 deletions packages/vuetify/src/components/VDataTable/VDataTableHeaders.tsx
@@ -1,15 +1,19 @@
// Components
import { VDataTableColumn } from './VDataTableColumn'
import { VCheckboxBtn } from '@/components/VCheckbox'
import { VChip } from '@/components/VChip'
import { VIcon } from '@/components/VIcon'
import { VSelect } from '@/components/VSelect'

// Composables
import { useHeaders } from './composables/headers'
import { useSelection } from './composables/select'
import { useSort } from './composables/sort'
import { useBackgroundColor } from '@/composables/color'
import { makeDisplayProps, useDisplay } from '@/composables/display'
import { IconValue } from '@/composables/icons'
import { LoaderSlot, makeLoaderProps, useLoader } from '@/composables/loader'
import { useLocale } from '@/composables/locale'

// Utilities
import { computed, mergeProps } from 'vue'
Expand All @@ -20,6 +24,7 @@ import type { CSSProperties, PropType, UnwrapRef } from 'vue'
import type { provideSelection } from './composables/select'
import type { provideSort } from './composables/sort'
import type { InternalDataTableHeader } from './types'
import type { ItemProps } from '@/composables/list-items'
import type { LoaderSlotProps } from '@/composables/loader'

export type HeadersSlotProps = {
Expand All @@ -34,7 +39,7 @@ export type HeadersSlotProps = {
isSorted: ReturnType<typeof provideSort>['isSorted']
}

type VDataTableHeaderCellColumnSlotProps = {
export type VDataTableHeaderCellColumnSlotProps = {
column: InternalDataTableHeader
selectAll: ReturnType<typeof provideSelection>['selectAll']
isSorted: ReturnType<typeof provideSort>['isSorted']
Expand Down Expand Up @@ -68,6 +73,7 @@ export const makeVDataTableHeadersProps = propsFactory({
type: Object as PropType<Record<string, any>>,
},

...makeDisplayProps(),
...makeLoaderProps(),
}, 'VDataTableHeaders')

Expand All @@ -77,6 +83,7 @@ export const VDataTableHeaders = genericComponent<VDataTableHeadersSlots>()({
props: makeVDataTableHeadersProps(),

setup (props, { slots }) {
const { t } = useLocale()
const { toggleSort, sortBy, isSorted } = useSort()
const { someSelected, allSelected, selectAll, showSelectAll } = useSelection()
const { columns, headers } = useHeaders()
Expand All @@ -102,6 +109,8 @@ export const VDataTableHeaders = genericComponent<VDataTableHeadersSlots>()({

const { backgroundColorClasses, backgroundColorStyles } = useBackgroundColor(props, 'color')

const { displayClasses, mobile } = useDisplay(props)

const slotProps = computed(() => ({
headers: headers.value,
columns: columns.value,
Expand All @@ -114,6 +123,15 @@ export const VDataTableHeaders = genericComponent<VDataTableHeadersSlots>()({
getSortIcon,
} satisfies HeadersSlotProps))

const headerCellClasses = computed(() => ([
'v-data-table__th',
{
'v-data-table__th--sticky': props.sticky,
},
displayClasses.value,
loaderClasses.value,
]))

const VDataTableHeaderCell = ({ column, x, y }: { column: InternalDataTableHeader, x: number, y: number }) => {
const noPadding = column.key === 'data-table-select' || column.key === 'data-table-expand'
const headerProps = mergeProps(props.headerProps ?? {}, column.headerProps ?? {})
Expand All @@ -123,14 +141,12 @@ export const VDataTableHeaders = genericComponent<VDataTableHeadersSlots>()({
tag="th"
align={ column.align }
class={[
'v-data-table__th',
{
'v-data-table__th--sortable': column.sortable,
'v-data-table__th--sorted': isSorted(column),
'v-data-table__th--fixed': column.fixed,
'v-data-table__th--sticky': props.sticky,
},
loaderClasses.value,
...headerCellClasses.value,
]}
style={{
width: convertToUnit(column.width),
Expand Down Expand Up @@ -203,8 +219,74 @@ export const VDataTableHeaders = genericComponent<VDataTableHeadersSlots>()({
)
}

useRender(() => {
const VDataTableMobileHeaderCell = () => {
const headerProps = mergeProps(props.headerProps ?? {} ?? {})

const displayItems = computed<ItemProps['items']>(() => {
return columns.value.filter(column => column?.sortable)
})

const appendIcon = computed(() => {
return allSelected.value ? '$checkboxOn' : someSelected.value ? '$checkboxIndeterminate' : '$checkboxOff'
})

return (
<VDataTableColumn
tag="th"
class={[
...headerCellClasses.value,
]}
colspan={ headers.value.length + 1 }
{ ...headerProps }
>
<div class="v-data-table-header__content">
<VSelect
chips
class="v-data-table__td-sort-select"
clearable
density="default"
items={ displayItems.value }
label={ t('$vuetify.dataTable.sortBy') }
multiple={ props.multiSort }
variant="underlined"
onClick:clear={ () => sortBy.value = [] }
appendIcon={ appendIcon.value }
onClick:append={ () => selectAll(!allSelected.value) }
>
{{
...slots,
chip: props => (
<VChip
onClick={ props.item.raw?.sortable ? () => toggleSort(props.item.raw) : undefined }
onMousedown={ (e: MouseEvent) => {
e.preventDefault()
e.stopPropagation()
}}
>
{ props.item.title }
<VIcon
class={[
'v-data-table__td-sort-icon',
isSorted(props.item.raw) && 'v-data-table__td-sort-icon-active',
]}
icon={ getSortIcon(props.item.raw) }
size="small"
/>
</VChip>
),
}}
</VSelect>
</div>
</VDataTableColumn>
)
}

useRender(() => {
return mobile.value ? (
<tr>
<VDataTableMobileHeaderCell />
</tr>
) : (
<>
{ slots.headers
? slots.headers(slotProps.value)
Expand Down

0 comments on commit 66880ce

Please sign in to comment.