From 5e4ef02ebfa251747f6e3c20624d327000000be4 Mon Sep 17 00:00:00 2001 From: Erik Tiekstra Date: Fri, 4 Oct 2024 11:51:39 +0200 Subject: [PATCH] feat(SW-441): Added table block component --- components/Blocks/Table/index.tsx | 97 +++++++++++++++++++ components/Blocks/Table/table.module.css | 6 ++ components/Blocks/index.tsx | 4 +- .../ContentPage/contentPage.module.css | 2 + .../TempDesignSystem/ScrollWrapper/index.tsx | 35 +++++++ .../ScrollWrapper/scrollWrapper.module.css | 33 +++++++ .../ScrollWrapper/scrollWrapper.ts | 2 + .../ShowMoreButton/button.module.css | 4 - .../TempDesignSystem/ShowMoreButton/index.tsx | 25 +++-- .../ShowMoreButton/showMoreButton.module.css | 23 +++++ .../ShowMoreButton/showMoreButton.ts | 11 +++ .../ShowMoreButton/variants.ts | 11 +++ components/TempDesignSystem/Table/TH.tsx | 10 +- components/TempDesignSystem/Table/index.tsx | 27 +++++- .../TempDesignSystem/Table/table.module.css | 39 +++++++- components/TempDesignSystem/Table/table.ts | 14 +++ components/TempDesignSystem/Table/variants.ts | 27 ++++++ hooks/useScrollShadows.ts | 36 +++++++ package-lock.json | 32 ++++++ package.json | 1 + .../contentstack/schemas/blocks/table.ts | 14 ++- types/components/blocks/table.ts | 5 + types/trpc/routers/contentstack/blocks.ts | 2 +- 23 files changed, 431 insertions(+), 29 deletions(-) create mode 100644 components/Blocks/Table/index.tsx create mode 100644 components/Blocks/Table/table.module.css create mode 100644 components/TempDesignSystem/ScrollWrapper/index.tsx create mode 100644 components/TempDesignSystem/ScrollWrapper/scrollWrapper.module.css create mode 100644 components/TempDesignSystem/ScrollWrapper/scrollWrapper.ts delete mode 100644 components/TempDesignSystem/ShowMoreButton/button.module.css create mode 100644 components/TempDesignSystem/ShowMoreButton/showMoreButton.module.css create mode 100644 components/TempDesignSystem/ShowMoreButton/showMoreButton.ts create mode 100644 components/TempDesignSystem/ShowMoreButton/variants.ts create mode 100644 components/TempDesignSystem/Table/table.ts create mode 100644 components/TempDesignSystem/Table/variants.ts create mode 100644 hooks/useScrollShadows.ts create mode 100644 types/components/blocks/table.ts diff --git a/components/Blocks/Table/index.tsx b/components/Blocks/Table/index.tsx new file mode 100644 index 000000000..1a84cae17 --- /dev/null +++ b/components/Blocks/Table/index.tsx @@ -0,0 +1,97 @@ +"use client" + +import { + flexRender, + getCoreRowModel, + getPaginationRowModel, + useReactTable, +} from "@tanstack/react-table" +import { useState } from "react" + +import ScrollWrapper from "@/components/TempDesignSystem/ScrollWrapper" +import ShowMoreButton from "@/components/TempDesignSystem/ShowMoreButton" +import Table from "@/components/TempDesignSystem/Table" + +import styles from "./table.module.css" + +import type { TableProps } from "@/types/components/blocks/table" + +export default function TableBlock({ data }: TableProps) { + const { columns, rows, totalWidth } = data + const initialPageSize = 5 + const [pageSize, setPageSize] = useState(initialPageSize) + const showMoreVisible = rows.length > initialPageSize + const showLessVisible = pageSize >= rows.length + const columnDefs = columns.map((col) => ({ + accessorKey: col.id, + header: col.header, + size: col.width, + })) + + const table = useReactTable({ + columns: columnDefs, + data: rows, + getCoreRowModel: getCoreRowModel(), + getPaginationRowModel: getPaginationRowModel(), + state: { + pagination: { + pageIndex: 0, + pageSize, + }, + }, + }) + + function handleShowMore() { + setPageSize(showLessVisible ? initialPageSize : rows.length) + } + + return ( +
+ + + + {table.getHeaderGroups().map((headerGroup) => ( + + {headerGroup.headers.map((header) => ( + + {flexRender( + header.column.columnDef.header, + header.getContext() + )} + + ))} + + ))} + + + {table.getRowModel().rows.map((row) => ( + + {row.getVisibleCells().map((cell) => ( + + {flexRender(cell.column.columnDef.cell, cell.getContext())} + + ))} + + ))} + +
+
+ {showMoreVisible ? ( + + ) : null} +
+ ) +} diff --git a/components/Blocks/Table/table.module.css b/components/Blocks/Table/table.module.css new file mode 100644 index 000000000..805cb9935 --- /dev/null +++ b/components/Blocks/Table/table.module.css @@ -0,0 +1,6 @@ +.tableWrapper { + display: grid; + border: 1px solid var(--Base-Border-Subtle); + border-radius: var(--Corner-radius-Small); + overflow: hidden; +} diff --git a/components/Blocks/index.tsx b/components/Blocks/index.tsx index efd0f2bcb..2423508d1 100644 --- a/components/Blocks/index.tsx +++ b/components/Blocks/index.tsx @@ -5,7 +5,7 @@ import TextCols from "@/components/Blocks/TextCols" import UspGrid from "@/components/Blocks/UspGrid" import JsonToHtml from "@/components/JsonToHtml" -import Table from "../Table" +import Table from "./Table" import type { BlocksProps } from "@/types/components/blocks" import { BlocksEnums } from "@/types/enums/blocks" @@ -50,7 +50,7 @@ export default function Blocks({ blocks }: BlocksProps) { /> ) case BlocksEnums.block.Table: - return + return
case BlocksEnums.block.TextCols: return case BlocksEnums.block.TextContent: diff --git a/components/ContentType/ContentPage/contentPage.module.css b/components/ContentType/ContentPage/contentPage.module.css index 026aa6065..aa0036f83 100644 --- a/components/ContentType/ContentPage/contentPage.module.css +++ b/components/ContentType/ContentPage/contentPage.module.css @@ -40,6 +40,8 @@ } .mainContent { + display: grid; + gap: var(--Spacing-x4); width: 100%; } diff --git a/components/TempDesignSystem/ScrollWrapper/index.tsx b/components/TempDesignSystem/ScrollWrapper/index.tsx new file mode 100644 index 000000000..1eb341f90 --- /dev/null +++ b/components/TempDesignSystem/ScrollWrapper/index.tsx @@ -0,0 +1,35 @@ +"use client" + +import { useMemo } from "react" + +import useScrollShadows from "@/hooks/useScrollShadows" + +import { ScrollWrapperProps } from "./scrollWrapper" + +import styles from "./scrollWrapper.module.css" + +export default function ScrollWrapper({ + className, + children, +}: ScrollWrapperProps) { + const { containerRef, showLeftShadow, showRightShadow } = useScrollShadows() + + const classNames = useMemo(() => { + const cls = [styles.scrollWrapper, className] + if (showLeftShadow) { + cls.push(styles.leftShadow) + } + if (showRightShadow) { + cls.push(styles.rightShadow) + } + return cls.join(" ") + }, [showLeftShadow, showRightShadow, className]) + + return ( +
+
+ {children} +
+
+ ) +} diff --git a/components/TempDesignSystem/ScrollWrapper/scrollWrapper.module.css b/components/TempDesignSystem/ScrollWrapper/scrollWrapper.module.css new file mode 100644 index 000000000..b604a3c24 --- /dev/null +++ b/components/TempDesignSystem/ScrollWrapper/scrollWrapper.module.css @@ -0,0 +1,33 @@ +.scrollWrapper { + position: relative; + overflow: hidden; +} + +.scrollWrapper::before, +.scrollWrapper::after { + content: ""; + position: absolute; + top: 0; + height: 100%; + pointer-events: none; + z-index: 1; + transition: opacity 0.2s ease; + opacity: 0; + width: 50px; +} + +.scrollWrapper.leftShadow::before { + left: 0; + background: linear-gradient(to right, rgba(0, 0, 0, 0.3), transparent); + opacity: 1; +} + +.scrollWrapper.rightShadow::after { + right: 0; + background: linear-gradient(to left, rgba(0, 0, 0, 0.3), transparent); + opacity: 1; +} + +.content { + overflow-x: auto; +} diff --git a/components/TempDesignSystem/ScrollWrapper/scrollWrapper.ts b/components/TempDesignSystem/ScrollWrapper/scrollWrapper.ts new file mode 100644 index 000000000..8440abfa5 --- /dev/null +++ b/components/TempDesignSystem/ScrollWrapper/scrollWrapper.ts @@ -0,0 +1,2 @@ +export interface ScrollWrapperProps + extends React.PropsWithChildren> {} diff --git a/components/TempDesignSystem/ShowMoreButton/button.module.css b/components/TempDesignSystem/ShowMoreButton/button.module.css deleted file mode 100644 index f55557f33..000000000 --- a/components/TempDesignSystem/ShowMoreButton/button.module.css +++ /dev/null @@ -1,4 +0,0 @@ -.container { - display: flex; - justify-content: center; -} diff --git a/components/TempDesignSystem/ShowMoreButton/index.tsx b/components/TempDesignSystem/ShowMoreButton/index.tsx index 6e9cae7bf..ca8a24dcb 100644 --- a/components/TempDesignSystem/ShowMoreButton/index.tsx +++ b/components/TempDesignSystem/ShowMoreButton/index.tsx @@ -5,18 +5,29 @@ import { useIntl } from "react-intl" import { ChevronDownIcon } from "@/components/Icons" import Button from "@/components/TempDesignSystem/Button" -import styles from "./button.module.css" +import { showMoreButtonVariants } from "./variants" -import type { ShowMoreButtonParams } from "@/types/components/myPages/stays/button" +import styles from "./showMoreButton.module.css" + +import type { ShowMoreButtonProps } from "./showMoreButton" export default function ShowMoreButton({ + className, + intent, disabled, + showLess, loadMoreData, -}: ShowMoreButtonParams) { - const { formatMessage } = useIntl() +}: ShowMoreButtonProps) { + const intl = useIntl() + const classNames = showMoreButtonVariants({ + className, + intent, + }) + return ( -
+
) diff --git a/components/TempDesignSystem/ShowMoreButton/showMoreButton.module.css b/components/TempDesignSystem/ShowMoreButton/showMoreButton.module.css new file mode 100644 index 000000000..32299ec80 --- /dev/null +++ b/components/TempDesignSystem/ShowMoreButton/showMoreButton.module.css @@ -0,0 +1,23 @@ +.container { + display: flex; + justify-content: center; +} + +.table { + display: grid; + justify-content: stretch; + border-top: 1px solid var(--Base-Border-Subtle); + background-color: var(--Base-Surface-Primary-light-Normal); +} + +.table .button { + border-radius: 0; +} + +.icon { + transition: transform 0.3s; +} + +.showLess .icon { + transform: rotate(180deg); +} diff --git a/components/TempDesignSystem/ShowMoreButton/showMoreButton.ts b/components/TempDesignSystem/ShowMoreButton/showMoreButton.ts new file mode 100644 index 000000000..b6ad929f5 --- /dev/null +++ b/components/TempDesignSystem/ShowMoreButton/showMoreButton.ts @@ -0,0 +1,11 @@ +import { showMoreButtonVariants } from "./variants" + +import type { VariantProps } from "class-variance-authority" + +export interface ShowMoreButtonProps + extends React.PropsWithChildren>, + VariantProps { + disabled?: boolean + showLess?: boolean + loadMoreData: () => void +} diff --git a/components/TempDesignSystem/ShowMoreButton/variants.ts b/components/TempDesignSystem/ShowMoreButton/variants.ts new file mode 100644 index 000000000..17f8620f1 --- /dev/null +++ b/components/TempDesignSystem/ShowMoreButton/variants.ts @@ -0,0 +1,11 @@ +import { cva } from "class-variance-authority" + +import styles from "./showMoreButton.module.css" + +export const showMoreButtonVariants = cva(styles.container, { + variants: { + intent: { + table: styles.table, + }, + }, +}) diff --git a/components/TempDesignSystem/Table/TH.tsx b/components/TempDesignSystem/Table/TH.tsx index ca2377193..657dc2b1d 100644 --- a/components/TempDesignSystem/Table/TH.tsx +++ b/components/TempDesignSystem/Table/TH.tsx @@ -1,7 +1,13 @@ import styles from "./table.module.css" -function TH({ children }: React.PropsWithChildren) { - return
+import type { THeadProps } from "./table" + +function TH({ children, width = "auto", ...props }: THeadProps) { + return ( + + ) } export default TH diff --git a/components/TempDesignSystem/Table/index.tsx b/components/TempDesignSystem/Table/index.tsx index 5ba745df5..fb5c4fde6 100644 --- a/components/TempDesignSystem/Table/index.tsx +++ b/components/TempDesignSystem/Table/index.tsx @@ -1,13 +1,34 @@ +import { TableProps } from "./table" import TBody from "./TBody" import TD from "./TD" import TH from "./TH" import THead from "./THead" import TR from "./TR" +import { tableVariants } from "./variants" -import styles from "./table.module.css" +function Table({ + className, + intent, + borderRadius, + variant, + layout, + width = "100%", + children, + ...props +}: TableProps) { + const classNames = tableVariants({ + className, + borderRadius, + intent, + layout, + variant, + }) -function Table({ children }: React.PropsWithChildren) { - return
{children} + {children} +
{children}
+ return ( + + {children} +
+ ) } Table.THead = THead diff --git a/components/TempDesignSystem/Table/table.module.css b/components/TempDesignSystem/Table/table.module.css index fcbb3fec2..2296b2ecd 100644 --- a/components/TempDesignSystem/Table/table.module.css +++ b/components/TempDesignSystem/Table/table.module.css @@ -1,20 +1,20 @@ .table { - border-radius: var(--Corner-radius-Medium); border-collapse: collapse; overflow: hidden; - width: 100%; + min-width: 100%; } .thead { - background-color: var(--Base-Background-Secondary-Normal, #f7e1d5); + color: var(--Base-Text-High-contrast); + background-color: var(--Base-Surface-Primary-dark-Normal); } .tbody { - background-color: var(--Base-Surface-Primary-light-Normal, #fff); + background-color: var(--Base-Surface-Primary-light-Normal); } .tr:not(:last-of-type) { - border-bottom: 1px solid var(--Primary-Light-On-Surface-Divider, #f0c1b6); + border-bottom: 1px solid var(--Base-Border-Subtle); } .th { @@ -28,6 +28,35 @@ padding: var(--Spacing-x2); } +.fixed { + table-layout: fixed; +} + +.smallRadius { + border-radius: var(--Corner-radius-Small); +} +.mediumRadius { + border-radius: var(--Corner-radius-Medium); +} +.largeRadius { + border-radius: var(--Corner-radius-Large); +} + +.content .thead { + background-color: var(--Base-Surface-Subtle-Hover); +} + +.content .tbody { + background-color: var(--Base-Background-Primary-Normal); +} + +.content.striped .tbody .tr:nth-child(odd) { + background-color: var(--Base-Surface-Subtle-Normal); +} +.content.striped .tbody .tr:nth-child(even) { + background-color: var(--Base-Background-Primary-Normal); +} + @media screen and (min-width: 768px) { .th { padding: var(--Spacing-x2) var(--Spacing-x3); diff --git a/components/TempDesignSystem/Table/table.ts b/components/TempDesignSystem/Table/table.ts new file mode 100644 index 000000000..f2759484d --- /dev/null +++ b/components/TempDesignSystem/Table/table.ts @@ -0,0 +1,14 @@ +import { tableVariants } from "./variants" + +import type { VariantProps } from "class-variance-authority" + +export interface TableProps + extends React.PropsWithChildren>, + VariantProps { + width?: string +} + +export interface THeadProps + extends React.PropsWithChildren> { + width?: string +} diff --git a/components/TempDesignSystem/Table/variants.ts b/components/TempDesignSystem/Table/variants.ts new file mode 100644 index 000000000..c70202dcf --- /dev/null +++ b/components/TempDesignSystem/Table/variants.ts @@ -0,0 +1,27 @@ +import { cva } from "class-variance-authority" + +import styles from "./table.module.css" + +export const tableVariants = cva(styles.table, { + variants: { + intent: { + light: styles.light, + striped: styles.striped, + }, + variant: { + content: styles.content, + }, + borderRadius: { + none: "", + small: styles.smallRadius, + medium: styles.mediumRadius, + large: styles.largeRadius, + }, + layout: { + fixed: styles.fixed, + }, + }, + defaultVariants: { + borderRadius: "medium", + }, +}) diff --git a/hooks/useScrollShadows.ts b/hooks/useScrollShadows.ts new file mode 100644 index 000000000..7073d110e --- /dev/null +++ b/hooks/useScrollShadows.ts @@ -0,0 +1,36 @@ +import { useEffect, useRef, useState } from "react" + +const useScrollShadows = () => { + const containerRef = useRef(null) + const [showLeftShadow, setShowLeftShadow] = useState(false) + const [showRightShadow, setShowRightShadow] = useState(false) + + useEffect(() => { + const handleScroll = () => { + const container = containerRef.current + if (!container) return + + setShowLeftShadow(container.scrollLeft > 0) + setShowRightShadow( + container.scrollLeft < container.scrollWidth - container.clientWidth + ) + } + + const container = containerRef.current + if (container) { + container.addEventListener("scroll", handleScroll) + } + + handleScroll() + + return () => { + if (container) { + container.removeEventListener("scroll", handleScroll) + } + } + }, []) + + return { containerRef, showLeftShadow, showRightShadow } +} + +export default useScrollShadows diff --git a/package-lock.json b/package-lock.json index 35805d69e..c12152ff6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -23,6 +23,7 @@ "@scandic-hotels/design-system": "git+https://x-token-auth:$DESIGN_SYSTEM_ACCESS_TOKEN@bitbucket.org/scandic-swap/design-system.git#v0.1.0-rc.9", "@t3-oss/env-nextjs": "^0.9.2", "@tanstack/react-query": "^5.28.6", + "@tanstack/react-table": "^8.20.5", "@trpc/client": "^11.0.0-rc.467", "@trpc/react-query": "^11.0.0-rc.467", "@trpc/server": "^11.0.0-rc.467", @@ -5971,6 +5972,37 @@ "react": "^18.0.0" } }, + "node_modules/@tanstack/react-table": { + "version": "8.20.5", + "resolved": "https://registry.npmjs.org/@tanstack/react-table/-/react-table-8.20.5.tgz", + "integrity": "sha512-WEHopKw3znbUZ61s9i0+i9g8drmDo6asTWbrQh8Us63DAk/M0FkmIqERew6P71HI75ksZ2Pxyuf4vvKh9rAkiA==", + "dependencies": { + "@tanstack/table-core": "8.20.5" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "react": ">=16.8", + "react-dom": ">=16.8" + } + }, + "node_modules/@tanstack/table-core": { + "version": "8.20.5", + "resolved": "https://registry.npmjs.org/@tanstack/table-core/-/table-core-8.20.5.tgz", + "integrity": "sha512-P9dF7XbibHph2PFRz8gfBKEXEY/HJPOhym8CHmjF8y3q5mWpKx9xtZapXQUWCgkqvsK0R46Azuz+VaxD4Xl+Tg==", + "engines": { + "node": ">=12" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, "node_modules/@testing-library/dom": { "version": "10.1.0", "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.1.0.tgz", diff --git a/package.json b/package.json index 2be8bd832..ab79709d3 100644 --- a/package.json +++ b/package.json @@ -39,6 +39,7 @@ "@scandic-hotels/design-system": "git+https://x-token-auth:$DESIGN_SYSTEM_ACCESS_TOKEN@bitbucket.org/scandic-swap/design-system.git#v0.1.0-rc.9", "@t3-oss/env-nextjs": "^0.9.2", "@tanstack/react-query": "^5.28.6", + "@tanstack/react-table": "^8.20.5", "@trpc/client": "^11.0.0-rc.467", "@trpc/react-query": "^11.0.0-rc.467", "@trpc/server": "^11.0.0-rc.467", diff --git a/server/routers/contentstack/schemas/blocks/table.ts b/server/routers/contentstack/schemas/blocks/table.ts index 74f144608..5109d487b 100644 --- a/server/routers/contentstack/schemas/blocks/table.ts +++ b/server/routers/contentstack/schemas/blocks/table.ts @@ -28,17 +28,20 @@ export const tableSchema = z.object({ }), }) .transform((data) => { + const totalWidth = data.column_widths.reduce( + (acc, width) => acc + width, + 0 + ) const columns = data.table.tableState.columns.map((col, idx) => ({ id: col.id, - Header: col.label || "", - accessor: col.accessor, - columnWidth: data.column_widths[idx] || 0, + header: col.label || "", + width: data.column_widths[idx] || 0, })) const rows = data.table.tableState.data.map((rowData) => { const transformedRow: Record = {} - columns.forEach((col) => { - transformedRow[col.accessor] = rowData[col.accessor] || "" + columns.forEach((header) => { + transformedRow[header.id] = rowData[header.id] || "" }) return transformedRow }) @@ -46,6 +49,7 @@ export const tableSchema = z.object({ return { columns, rows, + totalWidth, } }), }) diff --git a/types/components/blocks/table.ts b/types/components/blocks/table.ts new file mode 100644 index 000000000..ee7d961d0 --- /dev/null +++ b/types/components/blocks/table.ts @@ -0,0 +1,5 @@ +import type { TableData } from "@/types/trpc/routers/contentstack/blocks" + +export interface TableProps { + data: TableData +} diff --git a/types/trpc/routers/contentstack/blocks.ts b/types/trpc/routers/contentstack/blocks.ts index e1ff7e923..1c46dda93 100644 --- a/types/trpc/routers/contentstack/blocks.ts +++ b/types/trpc/routers/contentstack/blocks.ts @@ -14,6 +14,6 @@ export interface DynamicContent extends z.output {} export interface Shortcuts extends z.output {} export type Shortcut = Shortcuts["shortcuts"] export interface TableBlock extends z.output {} -export type Table = TableBlock["table"] +export type TableData = TableBlock["table"] export interface TextCols extends z.output {} export interface UspGrid extends z.output {}