diff --git a/CHANGELOG.md b/CHANGELOG.md index f19b654f1..d3ea90994 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,20 @@ All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. +## [6.12.0](https://github.com/dequelabs/cauldron/compare/v6.11.0...v6.12.0) (2024-12-04) + + +### Features + +* **react,styles:** add grid layout for table component ([#1748](https://github.com/dequelabs/cauldron/issues/1748)) ([983c89f](https://github.com/dequelabs/cauldron/commit/983c89f21b584f1cef2c405c9e2db03529edbec1)) +* **styles:** synced `table` ([#1745](https://github.com/dequelabs/cauldron/issues/1745)) ([4531fb6](https://github.com/dequelabs/cauldron/commit/4531fb687cf53a1f40aad11c1494b38b2b04038d)) +* **styles:** synced tabs with design ([#1741](https://github.com/dequelabs/cauldron/issues/1741)) ([242ac5d](https://github.com/dequelabs/cauldron/commit/242ac5d921b08476e1d65219c24c7ae0a9d432df)) + + +### Bug Fixes + +* **react:** fix non-text contrast with text fields ([#1756](https://github.com/dequelabs/cauldron/issues/1756)) ([afe4062](https://github.com/dequelabs/cauldron/commit/afe40620f9eaa560b55f03368034fd99d50068ec)) + ## [6.11.0](https://github.com/dequelabs/cauldron/compare/v6.10.1...v6.11.0) (2024-11-06) diff --git a/docs/pages/components/Table.mdx b/docs/pages/components/Table.mdx index 640786bd0..a81007b69 100644 --- a/docs/pages/components/Table.mdx +++ b/docs/pages/components/Table.mdx @@ -334,6 +334,115 @@ function SortableTableExample() { ``` +### Grid Layout + +The Table component supports an optional css grid layout that can specify column alignment and width definitions per column. + +```jsx example + + + + First Name + Last Name + Email + + + + + Frank + Zappa + frank@zappa.io + + + Duane + Allman + duane@almond.biz + + + Yamandu + Costa + yamandu_costa@gmail.br + + + Jimmy + Herring + jamesHerring@hotmail.gov + + +
+``` + +For column alignments, all cells will be positioned according to the alignment specified for that column, defaulting to `start`: + + + + + Alignment Type + Description + + + + + `start` + Aligns all cells within the column to the start. + + + `center` + Aligns all cells within the column to the center. + + + `end` + Aligns all cells within the column to the center. + + +
+ +For column sizing, the values are roughly equivalent to [track sizing for grid-template-columns](https://developer.mozilla.org/en-US/docs/Web/CSS/grid-template-columns) + and the following values are supported: + + + + + Width Type + Description + + + + + `auto` + Sizes the column between a range of `min-content` and `max-content`. + + + `max-content` + Will size the column respective to the largest cell. + + + `min-content` + Will size the column respective to the smallest cell. + + + `number` + Applies a fixed width to the column. + + + `fr` + Applies a flex value that sizes a column proportional to the remaining space from other columns. + + + `%` + Applies a percentage width to the column respective to the size of the table. + + +
+ +For more advanced usage, the width value does not need to be specified as part of a column but can be specified using `--table-grid-template-columns` to set the respective column sizes relative to the content displayed within the table. + ## Props ### Table @@ -346,6 +455,16 @@ function SortableTableExample() { name: 'variant', type: 'bordered', description: 'Use the bordered variant of Table' + }, + { + name: 'layout', + type: 'grid', + description: 'When set, uses a css grid layout instead of a table layout.' + }, + { + name: 'columns', + type: ['Array', 'number'], + description: 'Only allowed when the table has a grid layout. Sets the column widths and alignments for each column.' } ]} /> @@ -379,6 +498,11 @@ function SortableTableExample() { type: 'function', description: 'The function that the implementer passes in to control the change of sort direction.', defaultValue: 'sorted ascending' + }, + { + name: 'align', + type: ['start', 'center', 'end'], + description: 'Only allowed when the table has a grid layout. Overrides any column alignments for this table header.' } ]} /> @@ -416,4 +540,11 @@ function SortableTableExample() { \ No newline at end of file diff --git a/e2e/screenshots/combobox.png b/e2e/screenshots/combobox.png index d3d6162da..ffc84df2b 100644 Binary files a/e2e/screenshots/combobox.png and b/e2e/screenshots/combobox.png differ diff --git a/e2e/screenshots/searchfield.png b/e2e/screenshots/searchfield.png index a5cd35f61..3a021b00c 100644 Binary files a/e2e/screenshots/searchfield.png and b/e2e/screenshots/searchfield.png differ diff --git a/e2e/screenshots/select.png b/e2e/screenshots/select.png index f22cca16e..9e7fab82d 100644 Binary files a/e2e/screenshots/select.png and b/e2e/screenshots/select.png differ diff --git a/e2e/screenshots/textfield.png b/e2e/screenshots/textfield.png index 74d6b9ab7..c80797cb3 100644 Binary files a/e2e/screenshots/textfield.png and b/e2e/screenshots/textfield.png differ diff --git a/package.json b/package.json index 188c91e8d..3260c6b0f 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "cauldron", "private": true, - "version": "6.11.0", + "version": "6.12.0", "license": "MPL-2.0", "scripts": { "clean": "rimraf dist docs/dist", diff --git a/packages/react/package.json b/packages/react/package.json index 4ae35e2d9..333ff7361 100644 --- a/packages/react/package.json +++ b/packages/react/package.json @@ -1,6 +1,6 @@ { "name": "@deque/cauldron-react", - "version": "6.11.0", + "version": "6.12.0", "license": "MPL-2.0", "description": "Fully accessible react components library for Deque Cauldron", "homepage": "https://cauldron.dequelabs.com/", diff --git a/packages/react/src/components/Table/Table.tsx b/packages/react/src/components/Table/Table.tsx index 9d43043d9..6ebf04e5f 100644 --- a/packages/react/src/components/Table/Table.tsx +++ b/packages/react/src/components/Table/Table.tsx @@ -1,21 +1,102 @@ -import React, { TableHTMLAttributes } from 'react'; +import React, { useMemo } from 'react'; import classNames from 'classnames'; +import { TableProvider } from './TableContext'; -interface TableProps extends TableHTMLAttributes { +export type Column = { + align: ColumnAlignment; + width?: ColumnWidth; +}; +export type ColumnAlignment = 'start' | 'center' | 'end'; +export type ColumnWidth = + | 'auto' + | 'min-content' + | 'max-content' + | `${number}` + | `${number}%` + | `${number}fr`; +export type RowAlignment = 'start' | 'center'; + +type TableBaseProps = { + layout: never; + columns: never; + variant?: 'border'; +}; + +type TableGridProps = { + layout: 'grid'; + columns?: Array | number; variant?: 'border'; -} - -const Table = ({ children, className, variant, ...other }: TableProps) => ( - - {children} -
+}; + +type TableProps = (TableBaseProps | Partial) & + React.TableHTMLAttributes; + +const Table = React.forwardRef( + ( + { + children, + className, + variant, + layout, + columns: columnsProp = [], + style, + ...other + }, + ref + ) => { + const isGridLayout = layout === 'grid'; + const columns: Column[] = useMemo(() => { + if (typeof columnsProp === 'number') { + return columnsProp > 0 + ? Array(columnsProp).fill({ align: 'start' }) + : [{ align: 'start' }]; + } + + return columnsProp; + }, [columnsProp]); + + const styleTemplateColumns = useMemo(() => { + if (layout !== 'grid') { + return; + } + + if (!columns) { + return 'auto'; + } + + return columns + .map(({ width }) => width || 'auto') + .join(' '); + }, [layout, columns]); + + const tableGridStyles: React.CSSProperties = isGridLayout + ? ({ + '--table-grid-template-columns': styleTemplateColumns + } as React.CSSProperties) + : {}; + + return ( + + + {children} + +
+ ); + } ); Table.displayName = 'Table'; diff --git a/packages/react/src/components/Table/TableBody.tsx b/packages/react/src/components/Table/TableBody.tsx index 82adb696e..520bb70c8 100644 --- a/packages/react/src/components/Table/TableBody.tsx +++ b/packages/react/src/components/Table/TableBody.tsx @@ -1,14 +1,16 @@ -import React, { HTMLAttributes } from 'react'; +import React, { forwardRef } from 'react'; import classNames from 'classnames'; -const TableBody = ({ - children, - className, - ...other -}: HTMLAttributes) => ( - - {children} - +type TableBodyProps = React.HTMLAttributes & { + className?: string; +}; + +const TableBody = forwardRef( + ({ children, className, ...other }, ref) => ( + + {children} + + ) ); TableBody.displayName = 'TableBody'; diff --git a/packages/react/src/components/Table/TableCell.tsx b/packages/react/src/components/Table/TableCell.tsx index 6b04f835d..baa68df7e 100644 --- a/packages/react/src/components/Table/TableCell.tsx +++ b/packages/react/src/components/Table/TableCell.tsx @@ -1,14 +1,37 @@ -import React, { TdHTMLAttributes } from 'react'; +import type { ColumnAlignment } from './Table'; +import React, { forwardRef } from 'react'; import classNames from 'classnames'; +import { useTable } from './TableContext'; +import useTableGridStyles from './useTableGridStyles'; +import useSharedRef from '../../utils/useSharedRef'; -const TableCell = ({ - children, - className, - ...other -}: TdHTMLAttributes) => ( - - {children} - +interface TableCellProps + extends Omit, 'align'> { + align?: ColumnAlignment; +} + +const TableCell = forwardRef( + ({ children, className, align, style, ...other }, ref) => { + const tableCellRef = useSharedRef(ref); + const { layout, columns } = useTable(); + const tableGridStyles = useTableGridStyles({ + elementRef: tableCellRef, + align, + columns, + layout + }); + + return ( + + {children} + + ); + } ); TableCell.displayName = 'TableCell'; diff --git a/packages/react/src/components/Table/TableContext.tsx b/packages/react/src/components/Table/TableContext.tsx new file mode 100644 index 000000000..f98da72d9 --- /dev/null +++ b/packages/react/src/components/Table/TableContext.tsx @@ -0,0 +1,47 @@ +import React, { + createContext, + useContext, + useState, + useMemo, + useRef +} from 'react'; +import type { Column } from './Table'; + +type TableContext = { + layout: 'table' | 'grid'; + columns: Array; +}; + +type TableProvider = { + children: React.ReactNode; + layout: 'table' | 'grid'; + columns: Array; +}; + +const TableContext = createContext({ + layout: 'table', + columns: [] +}); + +function TableProvider({ + children, + layout, + columns +}: TableProvider): JSX.Element { + const { Provider } = TableContext as React.Context; + const contextValue: TableContext = useMemo( + () => ({ + layout, + columns + }), + [layout, columns] + ); + + return {children}; +} + +function useTable(): TableContext { + return useContext(TableContext); +} + +export { TableProvider, useTable }; diff --git a/packages/react/src/components/Table/TableFooter.tsx b/packages/react/src/components/Table/TableFooter.tsx index 9d198d82b..67b0923a2 100644 --- a/packages/react/src/components/Table/TableFooter.tsx +++ b/packages/react/src/components/Table/TableFooter.tsx @@ -1,14 +1,20 @@ -import React, { HTMLAttributes } from 'react'; +import React, { forwardRef } from 'react'; import classNames from 'classnames'; -const TableFooter = ({ - children, - className, - ...other -}: HTMLAttributes) => ( - - {children} - +type TableFooterProps = React.HTMLAttributes & { + className?: string; +}; + +const TableFooter = forwardRef( + ({ children, className, ...other }, ref) => ( + + {children} + + ) ); TableFooter.displayName = 'TableFooter'; diff --git a/packages/react/src/components/Table/TableHead.tsx b/packages/react/src/components/Table/TableHead.tsx index fff0e2db5..bc08fdfa0 100644 --- a/packages/react/src/components/Table/TableHead.tsx +++ b/packages/react/src/components/Table/TableHead.tsx @@ -1,14 +1,16 @@ -import React, { HTMLAttributes } from 'react'; +import React, { forwardRef } from 'react'; import classNames from 'classnames'; -const TableHead = ({ - children, - className, - ...other -}: HTMLAttributes) => ( - - {children} - +type TableHeadProps = React.HTMLAttributes & { + className?: string; +}; + +const TableHead = forwardRef( + ({ children, className, ...other }, ref) => ( + + {children} + + ) ); TableHead.displayName = 'TableHead'; diff --git a/packages/react/src/components/Table/TableHeader.tsx b/packages/react/src/components/Table/TableHeader.tsx index 56a107169..1eda1e030 100644 --- a/packages/react/src/components/Table/TableHeader.tsx +++ b/packages/react/src/components/Table/TableHeader.tsx @@ -1,74 +1,98 @@ -import React, { ThHTMLAttributes } from 'react'; +import type { ColumnAlignment } from './Table'; +import React, { forwardRef } from 'react'; import classNames from 'classnames'; import Offscreen from '../Offscreen'; import Icon from '../Icon'; +import { useTable } from './TableContext'; +import useTableGridStyles from './useTableGridStyles'; +import useSharedRef from '../../utils/useSharedRef'; // these match aria-sort's values type SortDirection = 'ascending' | 'descending' | 'none'; -interface TableHeaderProps extends ThHTMLAttributes { +interface TableHeaderProps + extends Omit, 'align'> { sortDirection?: SortDirection; onSort?: () => void; sortAscendingAnnouncement?: string; sortDescendingAnnouncement?: string; + align?: ColumnAlignment; } -const TableHeader = ({ - children, - sortDirection, - onSort, - className, - sortAscendingAnnouncement = 'sorted ascending', - sortDescendingAnnouncement = 'sorted descending', - ...other -}: TableHeaderProps) => { - // When the sort direction changes, we want to announce the change in a live region - // because changes to the sort value is not widely supported yet - // see: https://a11ysupport.io/tech/aria/aria-sort_attribute - const announcement = - sortDirection === 'ascending' - ? sortAscendingAnnouncement - : sortDirection === 'descending' - ? sortDescendingAnnouncement - : ''; +const TableHeader = forwardRef( + ( + { + children, + sortDirection, + onSort, + className, + sortAscendingAnnouncement = 'sorted ascending', + sortDescendingAnnouncement = 'sorted descending', + align, + style, + ...other + }, + ref + ) => { + const tableHeaderRef = useSharedRef(ref); + const { layout, columns } = useTable(); + const tableGridStyles = useTableGridStyles({ + elementRef: tableHeaderRef, + align, + columns, + layout + }); - return ( - - {!!onSort && !!sortDirection ? ( - - ) : ( - children - )} - - ); -}; + + + {announcement} + + + + ) : ( + children + )} + + ); + } +); TableHeader.displayName = 'TableHeader'; diff --git a/packages/react/src/components/Table/index.test.tsx b/packages/react/src/components/Table/index.test.tsx index 752a8c499..267da3f11 100644 --- a/packages/react/src/components/Table/index.test.tsx +++ b/packages/react/src/components/Table/index.test.tsx @@ -11,9 +11,9 @@ import Table, { } from './'; import axe from '../../axe'; -const renderDefaultTable = () => { +const renderDefaultTable = (props: React.ComponentProps = {}) => { return render( - +
@@ -164,6 +164,13 @@ test('should render the expected semantic HTML elements', () => { expect(tableFooter.tagName).toBe('TFOOT'); }); +test('should support ref prop', () => { + const ref = React.createRef(); + renderDefaultTable({ ref }); + expect(ref.current).toBeTruthy(); + expect(ref.current).toEqual(screen.getByRole('table')); +}); + test('should render with border variant', () => { render(
@@ -314,12 +321,79 @@ test('should maintain focus on the sort button after it is clicked', async () => }); }); -test('returns 0 axe violations', async () => { +test('should support grid layout', () => { + renderDefaultTable({ layout: 'grid', columns: 2 }); + const table = screen.getByRole('table'); + expect(table).toHaveClass('Table', 'TableGrid'); + expect(table).toHaveStyle('--table-grid-template-columns: auto auto'); +}); + +test('should support column definitions with grid layout', () => { + renderDefaultTable({ + layout: 'grid', + columns: [ + { width: '1fr', align: 'start' }, + { width: 'min-content', align: 'end' } + ] + }); + const table = screen.getByRole('table'); + expect(table).toHaveStyle('--table-grid-template-columns: 1fr min-content'); + const tableHeaderCells = screen.getAllByRole('columnheader'); + expect(tableHeaderCells[0]).toHaveStyle('text-align: start'); + expect(tableHeaderCells[1]).toHaveStyle('text-align: end'); + const tableCells = screen.getAllByRole('cell'); + expect(tableCells[0]).toHaveStyle('text-align: start'); + expect(tableCells[1]).toHaveStyle('text-align: end'); +}); + +test('should apply colspan styles to cells in grid layout', () => { + render( +
+ + + + 2 + + 1 + + +
+ ); + + expect(screen.getByTestId('colspan-cell')).toHaveStyle('gridColumn: span 2'); + expect(screen.getByTestId('cell')).not.toHaveStyle('gridColumn: span 1'); +}); + +test('should apply rowspan styles to cells in grid layout', () => { + render( + + + + + 2 + + 1 + + +
+ ); + + expect(screen.getByTestId('rowspan-cell')).toHaveStyle('gridRow: span 2'); + expect(screen.getByTestId('cell')).not.toHaveStyle('gridRow: span 1'); +}); + +test('returns 0 axe violations for default table layout', async () => { const { container } = renderDefaultTable(); const results = await axe(container); expect(results).toHaveNoViolations(); }); +test('returns 0 axe violations for grid table layout', async () => { + const { container } = renderDefaultTable({ layout: 'grid' }); + const results = await axe(container); + expect(results).toHaveNoViolations(); +}); + test('returns 0 axe violations without any sorting', async () => { const { container } = render( diff --git a/packages/react/src/components/Table/useTableGridStyles.ts b/packages/react/src/components/Table/useTableGridStyles.ts new file mode 100644 index 000000000..899692ce5 --- /dev/null +++ b/packages/react/src/components/Table/useTableGridStyles.ts @@ -0,0 +1,62 @@ +import type { useTable } from './TableContext'; +import type { Column, ColumnAlignment } from './Table'; +import { useState, useEffect } from 'react'; + +interface useTableGridStylesParameters< + E extends HTMLTableCellElement = HTMLTableCellElement +> { + elementRef: React.RefObject; + align?: ColumnAlignment; + layout: ReturnType['layout']; + columns: Array; +} + +export default function useTableGridStyles({ + elementRef, + align, + layout, + columns +}: useTableGridStylesParameters): React.CSSProperties { + const isGridLayout = layout === 'grid'; + const [columnAlignment, setColumnAlignment] = useState( + align || 'start' + ); + const [gridColumnSpan, setGridColumnSpan] = useState(1); + const [gridRowSpan, setGridRowSpan] = useState(1); + + useEffect(() => { + if (!isGridLayout) { + return; + } + + const element = elementRef.current; + const column = + typeof columns !== 'number' && columns[element?.cellIndex ?? -1]; + + if (!column) { + setColumnAlignment(align || 'start'); + } else { + setColumnAlignment(column.align); + } + + if (element?.colSpan) { + setGridColumnSpan(element.colSpan); + } else { + setGridColumnSpan(1); + } + + if (element?.rowSpan) { + setGridRowSpan(element.rowSpan); + } else { + setGridRowSpan(1); + } + }, [isGridLayout, columns, align]); + + return isGridLayout + ? { + textAlign: columnAlignment, + gridColumn: gridColumnSpan > 1 ? `span ${gridColumnSpan}` : undefined, + gridRow: gridRowSpan > 1 ? `span ${gridRowSpan}` : undefined + } + : {}; +} diff --git a/packages/styles/forms.css b/packages/styles/forms.css index eb9079a58..8b89d4422 100644 --- a/packages/styles/forms.css +++ b/packages/styles/forms.css @@ -1,7 +1,7 @@ :root { --field-background-color: var(--white); --field-content-color: var(--text-color-base); - --field-border-color: var(--gray-40); + --field-border-color: var(--gray-60); --field-border-color-underline: var(--gray-80); --field-border-color-hover: var(--gray-90); --field-border-color-focus: var(--accent-primary); diff --git a/packages/styles/package.json b/packages/styles/package.json index 6a3e8155f..264107d99 100644 --- a/packages/styles/package.json +++ b/packages/styles/package.json @@ -1,6 +1,6 @@ { "name": "@deque/cauldron-styles", - "version": "6.11.0", + "version": "6.12.0", "license": "MPL-2.0", "description": "deque cauldron pattern library styles", "repository": "https://github.com/dequelabs/cauldron", diff --git a/packages/styles/table.css b/packages/styles/table.css index 063969ec3..67bba1822 100644 --- a/packages/styles/table.css +++ b/packages/styles/table.css @@ -7,15 +7,17 @@ --table-header-sorting-text-color: var(--gray-90); --table-cell-text-color: var(--gray-60); --table-cell-background-color: var(--white); - --table-row-border-color: var(--gray-40); - --table-space: 11px; + --table-row-border-color: var(--gray-20); + --table-space: var(--space-small); --table-header-sorting-border-color: var(--gray-90); --table-box-shadow: 0 2px 3px rgba(0, 0, 0, 0.15); + --table-odd-row-background: var(--gray-10); + --table-sort-hover-focus-ring-color: var(--gray-90); } .cauldron--theme-dark { --table-header-text-color: var(--white); - --table-header-background-color: var(--accent-dark); + --table-header-background-color: var(--accent-medium); /* --table-header-sorting-text-color will be deprecated */ --table-header-sorting-text-color: var(--white); --table-header-sorting-background-color: #53636e; @@ -24,6 +26,8 @@ --table-cell-background-color: var(--accent-medium); --table-row-border-color: #5d676f; --table-header-sorting-border-color: var(--accent-info); + --table-odd-row-background: var(--gray-90); + --table-sort-hover-focus-ring-color: var(--accent-light); } .Table { @@ -35,22 +39,38 @@ overflow-wrap: break-word; } +.TableGrid { + display: grid; + grid-template-columns: var(--table-grid-template-columns, auto); + grid-auto-rows: auto; +} + +:where(.TableGrid) :is(.TableHead, .TableBody, .TableFooter, .TableRow) { + display: grid; + grid-template-columns: subgrid; + grid-column: -1 / 1; +} + .TableHeader { background: var(--table-header-background-color); font-weight: var(--font-weight-medium); color: var(--table-header-text-color); - border-bottom: 2px solid var(--table-row-border-color); + border-bottom: 1px solid var(--table-row-border-color); } .TableBody .TableHeader { border-bottom-width: 1px; - border-right: 2px solid var(--table-row-border-color); + border-right: 1px solid var(--table-row-border-color); } .TableBody .TableRow:last-child .TableHeader { border-bottom: none; } +.TableBody .TableRow:nth-child(odd) .TableCell { + background: var(--table-odd-row-background); +} + .TableHeader[aria-sort] { padding: 0; } @@ -65,24 +85,18 @@ display: flex; align-items: center; justify-content: flex-start; - padding: var(--table-space) var(--space-smallest); + padding: var(--table-space) var(--space-large); } .TableHeader__sort-button:hover { cursor: pointer; - background-color: var(--table-header-background-color-hover); + box-shadow: inset 0 0 0 1px var(--table-sort-hover-focus-ring-color); } .TableHeader__sort-button:focus { outline-offset: unset; } -.TableHeader--sort-ascending, -.TableHeader--sort-descending { - background: var(--table-header-sorting-background-color); - border-bottom: 3px solid var(--table-header-sorting-border-color); -} - /* These styles are not applying and will be deprecated */ .TableHeader--sort-ascending .TableHeader__sort-button, .TableHeader--sort-decscending .TableHeader__sort-button { @@ -101,9 +115,8 @@ } .TableCell, -.TableHeader, -.TableFooter { - padding: var(--table-space) var(--space-smallest); +.TableHeader { + padding: var(--table-space) var(--space-large); } .Table:not(.Table--border) .TableRow:last-child .TableCell { @@ -123,19 +136,27 @@ } .Table--border, -.Table--border .TableHeader, -.Table--border .TableFooter, -.Table--border .TableCell { +.Table--border :is(.TableHeader, .TableFooter, .TableCell) { border: 1px solid var(--gray-40); } +:where(.TableGrid).Table--border :is(.TableCell, .TableHeader) { + border: none; +} + +:where(.TableGrid).Table--border + :is(.TableBody, .TableHead, .TableFooter, .TableRow) { + grid-gap: 1px; + background-color: var(--table-row-border-color); +} + .Table--border .TableHeader { - border-bottom: 2px solid var(--gray-40); + border-bottom: 1px solid var(--gray-40); } .Table--border .TableBody .TableHeader { border-bottom-width: 1px; - border-right: 2px solid var(--table-row-border-color); + border-right: 1px solid var(--table-row-border-color); } .cauldron--theme-dark .Table--border, diff --git a/packages/styles/tabs.css b/packages/styles/tabs.css index 2cb2a7700..2b30f82cb 100644 --- a/packages/styles/tabs.css +++ b/packages/styles/tabs.css @@ -1,16 +1,19 @@ :root { - --tabs-border-color: var(--stroke-light); + --tabs-border-color: var(--gray-20); --tab-shadow-color: var(--accent-primary); --tab-inactive-background-color: #fff; - --tab-inactive-text-color: var(--gray-80); + --tab-inactive-text-color: var(--gray-60); --tab-active-background-color: #fff; --tab-hover-background-color: var(--gray-20); --tab-panel-color: var(--gray-80); - --tabs-active-text-color: var(--gray-90); + --tab-panel-vertical-padding: var(--space-smaller) var(--space-largest) + var(--space-largest); + --tab-panel-horizontal-padding: var(--space-large) var(--space-small); + --tabs-active-text-color: var(--accent-primary-active); } .cauldron--theme-dark { - --tabs-border-color: var(--stroke-dark); + --tabs-border-color: var(--accent-dark); --tab-shadow-color: var(--accent-info); --tab-inactive-background-color: var(--accent-medium); --tab-inactive-text-color: var(--accent-light); @@ -31,19 +34,23 @@ .Tabs--vertical ~ .TabPanel { vertical-align: top; display: inline-block; + padding: var(--tab-panel-vertical-padding); +} + +.Tabs--horizontal ~ .TabPanel { + padding: var(--tab-panel-horizontal-padding); } .Tabs--horizontal { width: 100%; - border: 1px solid var(--tabs-border-color); + border-bottom: 1px solid var(--tabs-border-color); background-color: var(--tab-inactive-background-color); - border-bottom: 0; } .Tablist { display: flex; flex-direction: row; - border-left: 1px solid var(--tabs-border-color); + border-right: 1px solid var(--tabs-border-color); } .Tabs--vertical .Tablist { @@ -61,9 +68,6 @@ height: 2.875rem; white-space: nowrap; list-style-type: none; - border-top: 1px solid var(--tabs-border-color); - border-right: 1px solid var(--tabs-border-color); - text-decoration: underline; background-color: var(--tab-inactive-background-color); color: var(--tab-inactive-text-color); padding: var(--space-small); @@ -81,22 +85,18 @@ border-top: 0; } -.cauldron--theme-light .Tabs--vertical .Tab:last-child { - border-bottom: 1px solid var(--tabs-border-color); -} - .Tab:hover { cursor: pointer; background-color: var(--tab-hover-background-color); - color: var(--tabs-active-text-color); + font-weight: var(--font-weight-medium); } .Tab--active { color: var(--tabs-active-text-color); background-color: var(--tab-active-background-color); - font-weight: var(--font-weight-medium); + font-weight: var(--font-weight-normal); text-decoration: none; - box-shadow: inset 0 4px 0 var(--tab-shadow-color); + box-shadow: inset 0 -2px 0 var(--tab-shadow-color); z-index: 1; } @@ -105,15 +105,13 @@ } .Tabs--vertical .Tab--active { - box-shadow: inset 4px 0 0 var(--tab-shadow-color); + box-shadow: inset 2px 0 0 var(--tab-shadow-color); } .TabPanel { overflow-wrap: break-word; color: var(--tab-panel-color); background-color: var(--tab-active-background-color); - border: 1px solid var(--tabs-border-color); - padding: 1rem; } .TabPanel > * { @@ -126,5 +124,5 @@ .Tabs--thin .Tab { height: 2.125rem; - padding: var(--space-smallest); + padding: var(--space-smallest) var(--space-small); } diff --git a/vpats/2024-11-06-cauldron.md b/vpats/2024-11-06-cauldron.md new file mode 100644 index 000000000..37f5cf502 --- /dev/null +++ b/vpats/2024-11-06-cauldron.md @@ -0,0 +1,65 @@ +# Cauldron Accessibility Conformance Report WCAG Edition + +**Name of Product**: Cauldron + +**Report Date**: 2024-11-06 + +## Table 1: Success Criteria, Level A + +| Criteria | Conformance Level | Remarks and Explanations | +| --- | --- | --- | +| [1.1.1 Non-text Content](http://www.w3.org/TR/WCAG20/#text-equiv-all) (Level A) | Supports | | +| [1.2.1 Audio-only and Video-only (Prerecorded)](http://www.w3.org/TR/WCAG20/#media-equiv-av-only-alt) (Level A) | Supports | | +| [1.2.2 Captions (Prerecorded)](http://www.w3.org/TR/WCAG20/#media-equiv-captions) (Level A) | Supports | | +| [1.2.3 Audio Description or Media Alternative (Prerecorded)](http://www.w3.org/TR/WCAG20/#media-equiv-audio-desc) (Level A) | Supports | | +| [1.3.1 Info and Relationships](http://www.w3.org/TR/WCAG20/#content-structure-separation-programmatic) (Level A) | Supports | | +| [1.3.2 Meaningful Sequence](http://www.w3.org/TR/WCAG20/#content-structure-separation-sequence) (Level A) | Supports | | +| [1.3.3 Sensory Characteristics](http://www.w3.org/TR/WCAG20/#content-structure-separation-understanding) (Level A) | Supports | | +| [1.4.1 Use of Color](http://www.w3.org/TR/WCAG20/#visual-audio-contrast-without-color) (Level A) | Supports | | +| [1.4.2 Audio Control](http://www.w3.org/TR/WCAG20/#visual-audio-contrast-dis-audio) (Level A) | Supports | | +| [2.1.1 Keyboard](http://www.w3.org/TR/WCAG20/#keyboard-operation-keyboard-operable) (Level A) | Supports | | +| [2.1.2 No Keyboard Trap](http://www.w3.org/TR/WCAG20/#keyboard-operation-trapping) (Level A) | Supports | | +| [2.1.4 Character Key Shortcuts](http://www.w3.org/TR/WCAG20/#keyboard-operation-keyboard-operable) (Level A) | Supports | | +| [2.2.1 Timing Adjustable](http://www.w3.org/TR/WCAG20/#time-limits-required-behaviors) (Level A) | Supports | | +| [2.2.2 Pause, Stop, Hide](http://www.w3.org/TR/WCAG20/#time-limits-pause) (Level A) | Supports | | +| [2.3.1 Three Flashes or Below Threshold](http://www.w3.org/TR/WCAG20/#seizure-does-not-violate) (Level A) | Supports | | +| [2.4.1 Bypass Blocks](http://www.w3.org/TR/WCAG20/#navigation-mechanisms-skip) (Level A) | Supports | | +| [2.4.2 Page Titled](http://www.w3.org/TR/WCAG20/#navigation-mechanisms-title) (Level A) | Supports | | +| [2.4.3 Focus Order](http://www.w3.org/TR/WCAG20/#navigation-mechanisms-focus-order) (Level A) | Supports | | +| [2.4.4 Link Purpose (In Context)](http://www.w3.org/TR/WCAG20/#navigation-mechanisms-refs) (Level A) | Supports | | +| [2.5.1 Pointer Gestures](http://www.w3.org/TR/WCAG20/#navigation-mechanisms-mult-loc) (Level A) | Supports | | +| [2.5.2 Pointer Cancellation](http://www.w3.org/TR/WCAG20/#navigation-mechanisms-mult-loc) (Level A) | Supports | | +| [2.5.3 Label in Name](http://www.w3.org/TR/WCAG20/#navigation-mechanisms-descriptive) (Level A) | Supports | | +| [2.5.4 Motion Actuation](http://www.w3.org/TR/WCAG20/#navigation-mechanisms-motion-actuation) (Level A) | Supports | | +| [3.1.1 Language of Page](http://www.w3.org/TR/WCAG20/#meaning-doc-lang-id) (Level A) | Supports | | +| [3.2.1 On Focus](http://www.w3.org/TR/WCAG20/#consistent-behavior-receive-focus) (Level A) | Supports | | +| [3.2.2 On Input](http://www.w3.org/TR/WCAG20/#consistent-behavior-unpredictable-change) (Level A) | Supports | | +| [3.3.1 Error Identification](http://www.w3.org/TR/WCAG20/#minimize-error-identified) (Level A) | Supports | | +| [3.3.2 Labels or Instructions](http://www.w3.org/TR/WCAG20/#minimize-error-cues) (Level A) | Supports | | +| [4.1.1 Parsing](http://www.w3.org/TR/WCAG20/#ensure-compat-parses) (Level A) | Supports | | +| [4.1.2 Name, Role, Value](http://www.w3.org/TR/WCAG20/#ensure-compat-rsv) (Level A) | Supports | | + +## Table 2: Success Criteria, Level AA + +| Criteria | Conformance Level | Remarks and Explanations | +| --- | --- | --- | +| [1.2.4 Captions (Prerecorded)](http://www.w3.org/TR/WCAG20/#media-equiv-captions) (Level AA) | Supports | | +| [1.2.5 Audio Description or Media Alternative (Prerecorded)](http://www.w3.org/TR/WCAG20/#media-equiv-audio-desc) (Level AA) | Supports | | +| [1.3.4 Orientation](http://www.w3.org/TR/WCAG20/#visual-audio-contrast-orientation) (Level AA) | Supports | | +| [1.3.5 Identify Input Purpose](http://www.w3.org/TR/WCAG20/#input-purposes) (Level AA) | Supports | | +| [1.4.3 Contrast (Minimum)](http://www.w3.org/TR/WCAG20/#visual-audio-contrast-contrast) (Level AA) | Supports | | +| [1.4.4 Resize text](http://www.w3.org/TR/WCAG20/#visual-audio-contrast-scale) (Level AA) | Supports | | +| [1.4.5 Images of Text](http://www.w3.org/TR/WCAG20/#visual-audio-contrast-text-presentation) (Level AA) | Supports | | +| [1.4.10 Reflow](http://www.w3.org/TR/WCAG20/#visual-audio-contrast-scale) (Level AA) | Supports | | +| [1.4.11 Non-text Contrast](http://www.w3.org/TR/WCAG20/#visual-audio-contrast-contrast) (Level AA) | Supports | | +| [1.4.12 Text Spacing](http://www.w3.org/TR/WCAG20/#visual-audio-contrast-spacing) (Level AA) | Supports | | +| [1.4.13 Content on Hover or Focus](http://www.w3.org/TR/WCAG20/#visual-audio-contrast-dis-audio) (Level AA) | Supports | | +| [2.4.5 Multiple Ways](http://www.w3.org/TR/WCAG20/#navigation-mechanisms-mult-loc) (Level AA) | Supports | | +| [2.4.6 Headings and Labels](http://www.w3.org/TR/WCAG20/#navigation-mechanisms-descriptive) (Level AA) | Partially Supports |
  • [[#1393] [A11y] - Programmatic label does not convey purpose of control](https://github.com/dequelabs/cauldron/issues/1393) (2024-03-08)
| +| [2.4.7 Focus Visible](http://www.w3.org/TR/WCAG20/#navigation-mechanisms-focus-visible) (Level AA) | Supports | | +| [3.1.2 Language of Parts](http://www.w3.org/TR/WCAG20/#meaning-doc-lang-id) (Level AA) | Supports | | +| [3.2.3 Consistent Navigation](http://www.w3.org/TR/WCAG20/#consistent-behavior-consistent-locations) (Level AA) | Supports | | +| [3.2.4 Consistent Identification](http://www.w3.org/TR/WCAG20/#consistent-behavior-consistent-functionality) (Level AA) | Supports | | +| [3.3.3 Error Suggestion](http://www.w3.org/TR/WCAG20/#minimize-error-suggestions) (Level AA) | Supports | | +| [3.3.4 Error Prevention (Legal, Financial, Data)](http://www.w3.org/TR/WCAG20/#minimize-error-reversible) (Level AA) | Supports | | +| [4.1.3 Status Messages](http://www.w3.org/TR/WCAG20/#ensure-compat-rsv) (Level AA) | Supports | | \ No newline at end of file