Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: make NeTable responsive and accessible #47

Merged
merged 4 commits into from
May 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 63 additions & 2 deletions src/components/NeTable.vue
Original file line number Diff line number Diff line change
@@ -1,9 +1,70 @@
<!--
Copyright (C) 2024 Nethesis S.r.l.
SPDX-License-Identifier: GPL-3.0-or-later
-->
<script lang="ts" setup>
import { provide, ref, type PropType } from 'vue'
import NeTableSkeleton from './NeTableSkeleton.vue'

export type Breakpoint = 'sm' | 'md' | 'lg' | 'xl' | '2xl'

const props = defineProps({
ariaLabel: {
type: String,
required: true
},
cardBreakpoint: {
type: String as PropType<Breakpoint>,
default: 'md'
},
loading: {
type: Boolean,
default: false
},
skeletonRows: {
type: Number,
default: 8
},
skeletonColumns: {
type: Number,
default: 4
}
})

// provide cardBreakpoint prop to children components
provide('cardBreakpoint', ref(props.cardBreakpoint))

const tableCardStyle: Record<Breakpoint, string> = {
sm: 'sm:table sm:divide-y sm:divide-gray-300 sm:dark:divide-gray-600',
md: 'md:table md:divide-y md:divide-gray-300 md:dark:divide-gray-600',
lg: 'lg:table lg:divide-y lg:divide-gray-300 lg:dark:divide-gray-600',
xl: 'xl:table xl:divide-y xl:divide-gray-300 xl:dark:divide-gray-600',
'2xl': '2xl:table 2xl:divide-y 2xl:divide-gray-300 2xl:dark:divide-gray-600'
}
</script>
<template>
<div class="overflow-x-auto rounded-lg border border-gray-300 shadow-sm dark:border-gray-600">
<table
class="w-full table-auto divide-y divide-gray-300 bg-white text-left text-sm font-normal text-gray-700 dark:divide-gray-600 dark:bg-gray-950 dark:text-gray-200"
role="grid"
:aria-label="ariaLabel"
:class="[
`grid w-full table-auto bg-white text-left text-sm font-normal text-gray-700 dark:bg-gray-950 dark:text-gray-200`,
tableCardStyle[cardBreakpoint]
]"
>
<slot />
<template v-if="loading">
<NeTableSkeleton
:rows="skeletonRows"
:columns="skeletonColumns"
:card-breakpoint="cardBreakpoint"
/>
</template>
<template v-else>
<slot />
</template>
</table>
</div>
<div v-if="$slots.paginator" class="mt-6 flex flex-row justify-end">
<slot name="paginator" />
</div>
</template>
23 changes: 22 additions & 1 deletion src/components/NeTableBody.vue
Original file line number Diff line number Diff line change
@@ -1,5 +1,26 @@
<!--
Copyright (C) 2024 Nethesis S.r.l.
SPDX-License-Identifier: GPL-3.0-or-later
-->
<script lang="ts" setup>
import { inject } from 'vue'
import type { Breakpoint } from './NeTable.vue'

// inject cardBreakpoint from NeTable.vue
const cardBreakpoint = inject('cardBreakpoint', 'md')

const tbodyCardStyle: Record<Breakpoint, string> = {
sm: 'sm:table-row-group',
md: 'md:table-row-group',
lg: 'lg:table-row-group',
xl: 'xl:table-row-group',
'2xl': '2xl:table-row-group'
}
</script>
<template>
<tbody class="divide-y divide-gray-300 dark:divide-gray-600">
<tbody
:class="[`block divide-y divide-gray-300 dark:divide-gray-600`, tbodyCardStyle[cardBreakpoint]]"
>
<slot />
</tbody>
</template>
42 changes: 41 additions & 1 deletion src/components/NeTableCell.vue
Original file line number Diff line number Diff line change
@@ -1,5 +1,45 @@
<!--
Copyright (C) 2024 Nethesis S.r.l.
SPDX-License-Identifier: GPL-3.0-or-later
-->
<script lang="ts" setup>
import { inject } from 'vue'
import type { Breakpoint } from './NeTable.vue'

defineProps({
dataLabel: {
// this attribute replaces table header in mobile viewport
type: String,
required: true
}
})

// inject cardBreakpoint from NeTable.vue
const cardBreakpoint = inject('cardBreakpoint', 'md')

const tdCardStyle: Record<Breakpoint, string> = {
sm: 'sm:table-cell',
md: 'md:table-cell',
lg: 'lg:table-cell',
xl: 'xl:table-cell',
'2xl': '2xl:table-cell'
}

const dataLabelCardStyle: Record<Breakpoint, string> = {
sm: 'sm:hidden',
md: 'md:hidden',
lg: 'lg:hidden',
xl: 'xl:hidden',
'2xl': '2xl:hidden'
}
</script>
<template>
<td class="px-6 py-4">
<td :data-label="dataLabel" :class="[`grid grid-cols-2 px-6 py-4`, tdCardStyle[cardBreakpoint]]">
<span
:class="[`font-medium text-gray-900 dark:text-gray-50`, dataLabelCardStyle[cardBreakpoint]]"
>
{{ dataLabel }}
</span>
<slot />
</td>
</template>
26 changes: 25 additions & 1 deletion src/components/NeTableHead.vue
Original file line number Diff line number Diff line change
@@ -1,5 +1,29 @@
<!--
Copyright (C) 2024 Nethesis S.r.l.
SPDX-License-Identifier: GPL-3.0-or-later
-->
<script lang="ts" setup>
import { inject } from 'vue'
import type { Breakpoint } from './NeTable.vue'

// inject cardBreakpoint from NeTable.vue
const cardBreakpoint = inject('cardBreakpoint', 'md')

const tbodyCardStyle: Record<Breakpoint, string> = {
sm: 'sm:table-header-group',
md: 'md:table-header-group',
lg: 'lg:table-header-group',
xl: 'xl:table-header-group',
'2xl': '2xl:table-header-group'
}
</script>
<template>
<thead class="bg-gray-100 font-medium text-gray-900 dark:bg-gray-800 dark:text-gray-50">
<thead
:class="[
`hidden bg-gray-100 font-medium text-gray-900 dark:bg-gray-800 dark:text-gray-50`,
tbodyCardStyle[cardBreakpoint]
]"
>
<tr>
<slot></slot>
</tr>
Expand Down
6 changes: 5 additions & 1 deletion src/components/NeTableHeadCell.vue
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
<!--
Copyright (C) 2024 Nethesis S.r.l.
SPDX-License-Identifier: GPL-3.0-or-later
-->
<template>
<th class="px-6 py-3">
<th scope="col" class="px-6 py-3">
<slot></slot>
</th>
</template>
21 changes: 20 additions & 1 deletion src/components/NeTableRow.vue
Original file line number Diff line number Diff line change
@@ -1,5 +1,24 @@
<!--
Copyright (C) 2024 Nethesis S.r.l.
SPDX-License-Identifier: GPL-3.0-or-later
-->
<script lang="ts" setup>
import { inject } from 'vue'
import type { Breakpoint } from './NeTable.vue'

// inject cardBreakpoint from NeTable.vue
const cardBreakpoint = inject('cardBreakpoint', 'md')

const trCardStyle: Record<Breakpoint, string> = {
sm: 'sm:table-row',
md: 'md:table-row',
lg: 'lg:table-row',
xl: 'xl:table-row',
'2xl': '2xl:table-row'
}
</script>
<template>
<tr>
<tr :class="[`grid`, trCardStyle[cardBreakpoint]]">
<slot />
</tr>
</template>
44 changes: 44 additions & 0 deletions src/components/NeTableSkeleton.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
<!--
Copyright (C) 2024 Nethesis S.r.l.
SPDX-License-Identifier: GPL-3.0-or-later
-->
<script lang="ts" setup>
import { type PropType } from 'vue'
import NeTableHead from './NeTableHead.vue'
import NeTableHeadCell from './NeTableHeadCell.vue'
import NeSkeleton from './NeSkeleton.vue'
import NeTableBody from './NeTableBody.vue'
import NeTableRow from './NeTableRow.vue'
import NeTableCell from './NeTableCell.vue'

export type Breakpoint = 'sm' | 'md' | 'lg' | 'xl' | '2xl'

defineProps({
rows: {
type: Number,
required: true
},
columns: {
type: Number,
required: true
},
cardBreakpoint: {
type: String as PropType<Breakpoint>,
default: 'md'
}
})
</script>
<template>
<NeTableHead :card-breakpoint="cardBreakpoint">
<NeTableHeadCell v-for="i in columns" :key="i">
<NeSkeleton size="lg" />
</NeTableHeadCell>
</NeTableHead>
<NeTableBody :card-breakpoint="cardBreakpoint">
<NeTableRow v-for="i in rows" :key="i" :card-breakpoint="cardBreakpoint">
<NeTableCell v-for="j in columns" :key="j" :card-breakpoint="cardBreakpoint" data-label="">
<NeSkeleton size="lg" />
</NeTableCell>
</NeTableRow>
</NeTableBody>
</template>
115 changes: 69 additions & 46 deletions stories/NeTable.stories.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,16 @@ const meta: Meta<typeof NeTable> = {
title: 'Visual/NeTable',
component: NeTable,
tags: ['autodocs'],
argTypes: {
cardBreakpoint: { control: 'inline-radio', options: ['sm', 'md', 'lg', 'xl', '2xl'] }
},
args: {
ariaLabel: 'Aria label for the table',
cardBreakpoint: 'md',
loading: false,
skeletonRows: 8,
skeletonColumns: 4
},
render: (args) => ({
components: {
NeTable,
Expand All @@ -25,54 +35,67 @@ const meta: Meta<typeof NeTable> = {
return { args }
},
template: `
<NeTable>
<NeTableHead>
<NeTableHeadCell>Game</NeTableHeadCell>
<NeTableHeadCell>Platform</NeTableHeadCell>
<NeTableHeadCell>Year</NeTableHeadCell>
</NeTableHead>
<NeTableBody>
<NeTableRow>
<NeTableCell>The Legend of Zelda: Breath of the Wild</NeTableCell>
<NeTableCell>Nintendo Switch</NeTableCell>
<NeTableCell>2017</NeTableCell>
</NeTableRow>
<NeTableRow>
<NeTableCell>Super Mario Odyssey</NeTableCell>
<NeTableCell>Nintendo Switch</NeTableCell>
<NeTableCell>2017</NeTableCell>
</NeTableRow>
<NeTableRow>
<NeTableCell>The Legend of Zelda: Ocarina of Time</NeTableCell>
<NeTableCell>Nintendo 64</NeTableCell>
<NeTableCell>1998</NeTableCell>
</NeTableRow>
<NeTableRow>
<NeTableCell>Super Mario 64</NeTableCell>
<NeTableCell>Nintendo 64</NeTableCell>
<NeTableCell>1996</NeTableCell>
</NeTableRow>
<NeTableRow>
<NeTableCell>The Legend of Zelda: A Link to the Past</NeTableCell>
<NeTableCell>Super Nintendo</NeTableCell>
<NeTableCell>1991</NeTableCell>
</NeTableRow>
<NeTableRow>
<NeTableCell>Super Mario World</NeTableCell>
<NeTableCell>Super Nintendo</NeTableCell>
<NeTableCell>1990</NeTableCell>
</NeTableRow>
<NeTableRow>
<NeTableCell>The Legend of Zelda</NeTableCell>
<NeTableCell>Nintendo Entertainment System</NeTableCell>
<NeTableCell>1986</NeTableCell>
</NeTableRow>
</NeTableBody>
</NeTable>
`
<NeTable v-bind="args">
<NeTableHead>
<NeTableHeadCell>Game</NeTableHeadCell>
<NeTableHeadCell>Platform</NeTableHeadCell>
<NeTableHeadCell>Year</NeTableHeadCell>
</NeTableHead>
<NeTableBody>
<NeTableRow>
<NeTableCell data-label="Game">The Legend of Zelda: Breath of the Wild</NeTableCell>
<NeTableCell data-label="Platform">Nintendo Switch</NeTableCell>
<NeTableCell data-label="Year">2017</NeTableCell>
</NeTableRow>
<NeTableRow>
<NeTableCell data-label="Game">Super Mario Odyssey</NeTableCell>
<NeTableCell data-label="Platform">Nintendo Switch</NeTableCell>
<NeTableCell data-label="Year">2017</NeTableCell>
</NeTableRow>
<NeTableRow>
<NeTableCell data-label="Game">The Legend of Zelda: Ocarina of Time</NeTableCell>
<NeTableCell data-label="Platform">Nintendo 64</NeTableCell>
<NeTableCell data-label="Year">1998</NeTableCell>
</NeTableRow>
<NeTableRow>
<NeTableCell data-label="Game">Super Mario 64</NeTableCell>
<NeTableCell data-label="Platform">Nintendo 64</NeTableCell>
<NeTableCell data-label="Year">1996</NeTableCell>
</NeTableRow>
<NeTableRow>
<NeTableCell data-label="Game">The Legend of Zelda: A Link to the Past</NeTableCell>
<NeTableCell data-label="Platform">Super Nintendo</NeTableCell>
<NeTableCell data-label="Year">1991</NeTableCell>
</NeTableRow>
<NeTableRow>
<NeTableCell data-label="Game">Super Mario World</NeTableCell>
<NeTableCell data-label="Platform">Super Nintendo</NeTableCell>
<NeTableCell data-label="Year">1990</NeTableCell>
</NeTableRow>
<NeTableRow>
<NeTableCell data-label="Game">The Legend of Zelda</NeTableCell>
<NeTableCell data-label="Platform">Nintendo Entertainment System</NeTableCell>
<NeTableCell data-label="Year">1986</NeTableCell>
</NeTableRow>
</NeTableBody>
</NeTable>`
})
}

export default meta

export const Default: StoryObj<typeof NeTable> = {}
export const Default: StoryObj<typeof NeTable> = {
args: {}
}

export const Loading: StoryObj<typeof NeTable> = {
args: {
loading: true
}
}

export const CardBreakpointXL: StoryObj<typeof NeTable> = {
args: {
cardBreakpoint: 'xl'
}
}