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) => (
-
+};
+
+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 (
+
+ );
+ }
);
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}
-
- {sortDirection === 'none' ? (
-
- ) : sortDirection === 'ascending' ? (
-
- ) : (
-
- )}
-
-
-
- {announcement}
+ // 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
+ : '';
+
+ return (
+
+ {!!onSort && !!sortDirection ? (
+
+ {children}
+
+ {sortDirection === 'none' ? (
+
+ ) : sortDirection === 'ascending' ? (
+
+ ) : (
+
+ )}
-
-
- ) : (
- 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(
+
+ );
+
+ 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(
+
+ );
+
+ 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