Skip to content

Commit

Permalink
Table enhancements - feats #230, #232, #242, #243 (#252)
Browse files Browse the repository at this point in the history
  • Loading branch information
sam-dassana authored Mar 11, 2021
1 parent 90a9350 commit ae4d20a
Show file tree
Hide file tree
Showing 18 changed files with 1,083 additions and 527 deletions.
873 changes: 439 additions & 434 deletions package-lock.json

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
"color": "^3.1.3",
"framer-motion": "^2.9.5",
"fuse.js": "^6.4.2",
"jsonpath-plus": "^5.0.4",
"lodash": "^4.17.20",
"moment": "^2.29.1",
"react": "^17.0.1",
Expand Down
1 change: 1 addition & 0 deletions rollup.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ export default {
'color',
'framer-motion',
'fuse.js',
'jsonpath-plus',
'lodash',
'moment',
'react',
Expand Down
14 changes: 7 additions & 7 deletions src/__snapshots__/storybook.test.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ exports[`Storyshots Avatar Icon 1`] = `
>
<svg
className=""
fill="#7E8083"
fill="#7E7F86"
height={32}
width={32}
>
Expand Down Expand Up @@ -624,7 +624,7 @@ exports[`Storyshots Icon Custom 1`] = `
className="light storyWrapper-0-2-2"
>
<img
alt="https://dummyimage.com/600x400/000/fff&text=Dassana"
alt=""
className=""
height={64}
src="https://dummyimage.com/600x400/000/fff&text=Dassana"
Expand All @@ -638,7 +638,7 @@ exports[`Storyshots Icon Predefined 1`] = `
>
<svg
className=""
fill="#7E8083"
fill="#7E7F86"
height={64}
width={64}
>
Expand Down Expand Up @@ -1823,7 +1823,7 @@ exports[`Storyshots Popover Default 1`] = `
>
<svg
className=""
fill="#7E8083"
fill="#7E7F86"
height={32}
width={32}
>
Expand Down Expand Up @@ -1854,7 +1854,7 @@ exports[`Storyshots Popover Title 1`] = `
>
<svg
className=""
fill="#7E8083"
fill="#7E7F86"
height={32}
width={32}
>
Expand Down Expand Up @@ -2388,7 +2388,7 @@ exports[`Storyshots Select Icon 1`] = `
>
<svg
className=""
fill="#7E8083"
fill="#7E7F86"
height={15}
width={15}
>
Expand Down Expand Up @@ -3310,7 +3310,7 @@ exports[`Storyshots Tooltip Default 1`] = `
>
<svg
className=""
fill="#7E8083"
fill="#7E7F86"
height={32}
width={32}
>
Expand Down
5 changes: 2 additions & 3 deletions src/components/Icon/Icon.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ describe('Predefined Icon', () => {

describe('Custom Icon', () => {
const mockProps: IconProps = {
altText: 'foo',
height: 64,
icon: 'https://dummyimage.com/600x400/000/fff&text=Dassana'
}
Expand All @@ -69,9 +70,7 @@ describe('Custom Icon', () => {
})

it('has the correct alt attribute', () => {
expect(wrapper.getDOMNode().getAttribute('alt')).toBe(
'https://dummyimage.com/600x400/000/fff&text=Dassana'
)
expect(wrapper.getDOMNode().getAttribute('alt')).toBe('foo')
})

it('has the correct height', () => {
Expand Down
19 changes: 14 additions & 5 deletions src/components/Icon/index.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
import cn from 'classnames'
import { styleguide } from 'components/assets/styles'
import Icons, { IconName } from './IconsMap'
import React, { FC } from 'react'

const {
colors: { blacks }
} = styleguide

export interface SharedIconProps {
/**
* Array of classes to pass to element
Expand All @@ -15,6 +20,7 @@ export interface SharedIconProps {
}

interface IconPath extends SharedIconProps {
altText?: string
/**
* The url of the icon if rendering a custom icon.
*/
Expand All @@ -36,13 +42,12 @@ export type IconProps = IconKey | IconPath
export const Icon: FC<IconProps> = ({ height = 32, ...props }: IconProps) => {
const { classes = [] } = props

const { icon } = props as IconPath
const { iconKey } = props as IconKey
if (props.iconKey) {
const { iconKey } = props

if (iconKey) {
const Svg = Icons[iconKey] ? Icons[iconKey] : Icons.dassana

const { color = '#7E8083' } = props as IconKey
const { color = blacks['lighten-40'] } = props as IconKey

return (
<Svg
Expand All @@ -54,7 +59,11 @@ export const Icon: FC<IconProps> = ({ height = 32, ...props }: IconProps) => {
)
}

return <img alt={icon} className={cn(classes)} height={height} src={icon} />
const { altText = '', icon } = props

return (
<img alt={altText} className={cn(classes)} height={height} src={icon} />
)
}

export type { IconName }
3 changes: 1 addition & 2 deletions src/components/Select/OptionChildren.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import cn from 'classnames'
import { createUseStyles } from 'react-jss'
import { styleguide } from '../assets/styles/styleguide'
import { Tooltip } from '../Tooltip'
Expand Down Expand Up @@ -68,7 +67,7 @@ export const OptionChildren: FC<OptionChildrenProps> = ({
{children && children}
{iconKey && renderIcon(iconKey, optionsConfig)}
<span
className={cn(classes.optionText)}
className={classes.optionText}
onMouseEnter={(e: SyntheticEvent) => {
const el = e.currentTarget as HTMLElement

Expand Down
51 changes: 51 additions & 0 deletions src/components/Table/CellWithTooltip.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { createUseStyles } from 'react-jss'
import { Tooltip } from 'components/Tooltip'
import React, { FC, SyntheticEvent, useState } from 'react'

const useStyles = createUseStyles({
container: { display: 'grid', placeItems: 'stretch' },
text: {
display: 'block',
overflow: 'hidden',
textOverflow: 'ellipsis',
whiteSpace: 'nowrap'
},
wrapper: {
overflow: 'hidden',
textOverflow: 'ellipsis',
whiteSpace: 'nowrap'
}
})

interface CellWithTooltipProps {
text: string
}

export const CellWithTooltip: FC<CellWithTooltipProps> = ({
text
}: CellWithTooltipProps) => {
const [hasTooltip, setHasTooltip] = useState(false)

const classes = useStyles()

return (
<div className={classes.container}>
<div
className={classes.wrapper}
onMouseEnter={(e: SyntheticEvent) => {
const el = e.currentTarget as HTMLElement

setHasTooltip(el.scrollWidth > el.offsetWidth)
}}
>
{hasTooltip ? (
<Tooltip placement='bottomLeft' title={text}>
{text}
</Tooltip>
) : (
text
)}
</div>
</div>
)
}
100 changes: 100 additions & 0 deletions src/components/Table/MultipleIcons.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
import cn from 'classnames'
import { createUseStyles } from 'react-jss'
import { defaultIconHeight } from './utils'
import { styleguide } from 'components/assets/styles'
import { Tooltip } from 'components/Tooltip'
import { Icon, IconProps } from '../Icon'
import React, { FC } from 'react'

const { spacing } = styleguide

const useStyles = createUseStyles({
count: { borderBottom: '1px solid', marginLeft: spacing.s },
icon: {
'&:not(:last-child)': {
marginRight: spacing.s
}
},
iconInTooltip: {
marginBottom: spacing.s,
marginRight: spacing.s
},
tooltip: {
'&.ant-tooltip': {
'& > .ant-tooltip-content > .ant-tooltip-inner': {
padding: `${spacing.s}px 0 0 ${spacing.s}px`
},
maxWidth: 250 - spacing.s
}
}
})

interface Props {
iconPropsArr: IconProps[]
height?: number
truncateLength?: number
}

export const MultipleIcons: FC<Props> = ({
iconPropsArr = [],
height = defaultIconHeight,
truncateLength = 2
}: Props) => {
const classes = useStyles()

interface RenderIcons {
sliceStartIndex: number
sliceEndIndex?: number
isInsideTooltip?: boolean
}
const renderIcons = ({
sliceStartIndex,
sliceEndIndex,
isInsideTooltip
}: RenderIcons) =>
iconPropsArr
.slice(sliceStartIndex, sliceEndIndex)
.map((iconProps, i) => {
return (
<Icon
{...iconProps}
classes={[
cn({
[classes.icon]: true,
[classes.iconInTooltip]: isInsideTooltip
})
]}
height={height}
key={i}
/>
)
})

return (
<>
{renderIcons({ sliceEndIndex: truncateLength, sliceStartIndex: 0 })}
{truncateLength < iconPropsArr.length && (
<Tooltip
classes={[classes.tooltip]}
placement='bottom'
renderWithoutDataTag
title={renderIcons({
isInsideTooltip: true,
sliceStartIndex: truncateLength
})}
triggerMode='click'
>
<span
className={classes.count}
onClick={e =>
// this is to prevent the entire row from being clicked
e.stopPropagation()
}
>
+{iconPropsArr.length - truncateLength}
</span>
</Tooltip>
)}
</>
)
}
7 changes: 7 additions & 0 deletions src/components/Table/Table.stories.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -728,3 +728,10 @@ export type type ColumnType = StringType | NumberType | ComponentType
<Canvas>
<Story story={stories.EditableCells} name='Editable Cells' />
</Canvas>

<Canvas>
<Story
story={stories.MultipleIconsAndJSONPath}
name='Multiple Icons and JSONPath'
/>
</Canvas>
10 changes: 10 additions & 0 deletions src/components/Table/Table.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import tableData2, { Client } from './fixtures/2_sample_data'
import tableData3, { Client1 } from './fixtures/3_sample_data'
import tableData5, { Dot } from './fixtures/5_sample_data'
import tableData6, { Client2 } from './fixtures/6_sample_data'
import tableData7, { JSONPathData } from './fixtures/7_sample_data'

const { spacing } = styleguide

Expand Down Expand Up @@ -281,3 +282,12 @@ const EditableCellsTemplate: Story<TableProps<Client2>> = args => (
export const EditableCells = EditableCellsTemplate.bind({})
EditableCells.args = tableData6
EditableCells.argTypes = commonArgTypes

const MultipleIconsAndJSONPathTemplate: Story<TableProps<
JSONPathData
>> = args => <DecoratedTableStory<JSONPathData> {...args} />
export const MultipleIconsAndJSONPath = MultipleIconsAndJSONPathTemplate.bind(
{}
)
MultipleIconsAndJSONPath.args = tableData7
MultipleIconsAndJSONPath.argTypes = commonArgTypes
38 changes: 37 additions & 1 deletion src/components/Table/__tests__/Table.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { Input as AntDInput, Table as AntDTable } from 'antd'
import mockData, { Data, dateFormat } from '__mocks__/table_mock_data'
import mockData0, { Person } from '../fixtures/0_sample_data'
import mockData1, { File } from '../fixtures/4_sample_data'
import mockData2, { JSONPathData } from '../fixtures/7_sample_data'
import { mount, ReactWrapper, shallow, ShallowWrapper } from 'enzyme'
import { Table, TableProps } from '..'

Expand Down Expand Up @@ -108,7 +109,42 @@ describe('Table', () => {
toggle: false
}

expect(expected).toMatchObject(renderedData(wrapper)[0])
expect(expected).toEqual(renderedData(wrapper)[0])
})

it('renders all types and formats of data if col dataIndex is a JSONPath', () => {
const { columns, data } = mockData2

wrapper = mount(
createTable<JSONPathData>({
columns: columns,
data: [data[0]]
})
)

const expected = {
_FORMATTED_DATA: [
formatDate({
displayFormat: dateFormat,
unixTS: 1519782342212
})
],
company: { id: 'c1', name: 'azure', value: 'azure' },
id: 0,
key: 0,
name: { id: 'n1', value: 'Lorem ipsum' },
'name.value': 'Lorem ipsum',
start_date: { date: 1519782342212, id: 'sd1' },
'start_date.date': 1519782342212,
vendors: [
{
id: 'v1',
value: 'https://dummyimage.com/300x300/a92323/fff&text=C'
}
]
}

expect(expected).toEqual(renderedData(wrapper)[0])
})
})

Expand Down
Loading

0 comments on commit ae4d20a

Please sign in to comment.