diff --git a/src/components/Table.tsx b/src/components/Table.tsx index 3d8e14af..73193ee8 100644 --- a/src/components/Table.tsx +++ b/src/components/Table.tsx @@ -1,3 +1,4 @@ +import { gray4, gray6 } from '@taskany/colors'; import styled, { css } from 'styled-components'; const maxCols = 12; @@ -17,36 +18,53 @@ interface JustifyProps { justify: 'start' | 'center' | 'end' | 'between' | 'around' | 'baseline'; } -export type TableCellProps = - | { - /** Size of column width in range from `1` to `12` */ - col: number; - /** Minimum size column, use instead of `col` prop */ - min?: never; - /** Absolute or relative width, ex. `10ch` or `100` */ - width?: never; - } - | { - col?: never; - min: boolean; - width?: never; - } - | { - col?: never; - min?: never; - width: number | string; - } - | { - col?: never; - min?: never; - width?: never; - }; +export type TableCellProps = Partial & + ( + | { + /** Size of column width in range from `1` to `12` */ + col: number; + min?: never; + width?: never; + } + | { + col?: never; + /** Minimum size column, use instead of `col` prop */ + min: true; + width?: never; + } + | { + col?: never; + min?: never; + /** Absolute or relative width, ex. `10ch` or `100` */ + width: number | string; + } + | { + col?: never; + min?: never; + width?: never; + } + ); export interface TableProps extends GapProps { /** Container width */ width?: number; } +export type TableRowProps = GapProps & + Partial & + ( + | { + interactive?: false | never; + focused?: never; + } + | { + /** Apply interactive styles: hover */ + interactive: true; + /** Prop that mark current row as focused, ex. for keyboard navigation */ + focused: boolean; + } + ); + const mapRuleSet: { [key in AlignProps['align'] | JustifyProps['justify']]: string } = { baseline: 'baseline', start: 'flex-start', @@ -73,7 +91,7 @@ export const Table = styled.div` `} `; -export const TableRow = styled.div & GapProps>` +export const TableRow = styled.div` display: flex; flex-basis: 100%; align-items: flex-start; @@ -96,9 +114,24 @@ export const TableRow = styled.div & GapProps css` gap: ${gap}px; `} + + ${({ interactive }) => + interactive && + css` + &:hover { + background-color: ${gray6}; + } + `} + + ${({ focused, interactive }) => + interactive && + focused && + css` + background-color: ${gray4}; + `} `; -export const TableCell = styled.div>` +export const TableCell = styled.div` display: inline-flex; align-items: baseline; flex-wrap: wrap; diff --git a/src/stories/Table.stories.tsx b/src/stories/Table.stories.tsx index 3b085536..0be5a529 100644 --- a/src/stories/Table.stories.tsx +++ b/src/stories/Table.stories.tsx @@ -1,7 +1,7 @@ -import React, { useState } from 'react'; +import React, { useEffect, useState } from 'react'; import type { Meta, StoryFn } from '@storybook/react'; -import { gray5, gray3 } from '@taskany/colors'; -import styled from 'styled-components'; +import { gray5, gray3, gapS, radiusM } from '@taskany/colors'; +import styled, { css } from 'styled-components'; import { Table, TableRow, TableCell } from '../components/Table'; import { GoalIcon } from '../components/Icon/GoalIcon'; @@ -11,6 +11,7 @@ import { Dot } from '../components/Dot'; import { Tag } from '../components/Tag'; import { CircleProgressBar } from '../components/CircleProgressBar'; import { Button } from '../components/Button'; +import { useKeyPress } from '../hooks/useKeyPress'; const meta: Meta = { title: 'Table', @@ -41,9 +42,23 @@ const data = Array.from({ length: 10 }, (_, i) => ({ })); const StyledTableRow = styled(TableRow)<{ depth?: number }>` - padding: 5px 0; - border-bottom: 1px solid ${gray5}; - border-top: 1px solid ${gray3}; + padding: ${gapS}; + + ${({ interactive }) => + // for interactive rows append border-radius style + interactive && + css` + border-radius: ${radiusM}; + `} + + ${({ interactive }) => + // for non-interactive rows append border styles + // for explicit separation of rows + !interactive && + css` + border-bottom: 1px solid ${gray5}; + border-top: 1px solid ${gray3}; + `} &:first-child { border-top: 0; @@ -137,3 +152,64 @@ export const RecursiveTable = () => { ); }; + +export const FocusAndHover: Story = () => { + const [activeIndex, setActiveIndex] = useState(0); + + const downPress = useKeyPress('ArrowDown'); + const upPress = useKeyPress('ArrowUp'); + + useEffect(() => { + setActiveIndex((prev) => { + if (upPress) { + return prev > 0 ? prev - 1 : prev; + } + + return prev; + }); + }, [upPress]); + + useEffect(() => { + setActiveIndex((prev) => { + if (downPress) { + return prev < data.length - 1 ? prev + 1 : prev; + } + + return prev; + }); + }, [downPress]); + + return ( + + {data.map(({ title, projectId, tags, progress }, index) => ( + + + + + + + + {title} + + + + + + + + {projectId} + + + + {tags.map((t) => ( + + ))} + + + + + + ))} +
+ ); +};