diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/table_header/index.ts b/x-pack/plugins/enterprise_search/public/applications/shared/table_header/index.ts new file mode 100644 index 0000000000000..34ce070fcde46 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/shared/table_header/index.ts @@ -0,0 +1,7 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export { TableHeader } from './table_header'; diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/table_header/table_header.test.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/table_header/table_header.test.tsx new file mode 100644 index 0000000000000..70e2ac7ac6f0d --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/shared/table_header/table_header.test.tsx @@ -0,0 +1,29 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { shallow } from 'enzyme'; +import { EuiTableHeader, EuiTableHeaderCell } from '@elastic/eui'; + +import { TableHeader } from './table_header'; + +const headerItems = ['foo', 'bar', 'baz']; + +describe('TableHeader', () => { + it('renders', () => { + const wrapper = shallow(); + + expect(wrapper.find(EuiTableHeader)).toHaveLength(1); + expect(wrapper.find(EuiTableHeaderCell)).toHaveLength(3); + }); + + it('renders extra cell', () => { + const wrapper = shallow(); + + expect(wrapper.find(EuiTableHeader)).toHaveLength(1); + expect(wrapper.find(EuiTableHeaderCell)).toHaveLength(4); + }); +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/table_header/table_header.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/table_header/table_header.tsx new file mode 100644 index 0000000000000..e7f9617fdcd91 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/shared/table_header/table_header.tsx @@ -0,0 +1,23 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; + +import { EuiTableHeader, EuiTableHeaderCell } from '@elastic/eui'; + +interface ITableHeaderProps { + headerItems: string[]; + extraCell?: boolean; +} + +export const TableHeader: React.FC = ({ headerItems, extraCell }) => ( + + {headerItems.map((item, i) => ( + {item} + ))} + {extraCell && } + +); diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/truncate/index.ts b/x-pack/plugins/enterprise_search/public/applications/shared/truncate/index.ts new file mode 100644 index 0000000000000..d3ee618e92b5b --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/shared/truncate/index.ts @@ -0,0 +1,8 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export { truncate, truncateBeginning } from './truncate'; +export { TruncatedContent } from './truncated_content'; diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/truncate/truncate.test.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/truncate/truncate.test.tsx new file mode 100644 index 0000000000000..aa8427cd822be --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/shared/truncate/truncate.test.tsx @@ -0,0 +1,47 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { shallow } from 'enzyme'; + +import { TruncatedContent } from './'; + +const content = 'foobarbaz'; + +describe('TruncatedContent', () => { + it('renders with no truncation', () => { + const wrapper = shallow(); + + expect(wrapper.find('span.truncated-content')).toHaveLength(0); + expect(wrapper.text()).toEqual('foo'); + }); + + it('renders with truncation at the end', () => { + const wrapper = shallow(); + const element = wrapper.find('span.truncated-content'); + + expect(element).toHaveLength(1); + expect(element.prop('title')).toEqual(content); + expect(wrapper.text()).toEqual('foob…'); + expect(wrapper.find('span.truncated-content__tooltip')).toHaveLength(0); + }); + + it('renders with truncation at the beginning', () => { + const wrapper = shallow( + + ); + + expect(wrapper.find('span.truncated-content')).toHaveLength(1); + expect(wrapper.text()).toEqual('…rbaz'); + }); + + it('renders with inline tooltip', () => { + const wrapper = shallow(); + + expect(wrapper.find('span.truncated-content').prop('title')).toEqual(''); + expect(wrapper.find('span.truncated-content__tooltip')).toHaveLength(1); + }); +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/truncate/truncate.ts b/x-pack/plugins/enterprise_search/public/applications/shared/truncate/truncate.ts new file mode 100644 index 0000000000000..36094e3abe258 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/shared/truncate/truncate.ts @@ -0,0 +1,13 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export function truncate(text: string, length: number) { + return `${text.substring(0, length)}…`; +} + +export function truncateBeginning(text: string, length: number) { + return `…${text.substring(text.length - length)}`; +} diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/truncate/truncated_content.scss b/x-pack/plugins/enterprise_search/public/applications/shared/truncate/truncated_content.scss new file mode 100644 index 0000000000000..701834acfed9d --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/shared/truncate/truncated_content.scss @@ -0,0 +1,35 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +.truncated-content { + position: relative; + z-index: 2; + display: inline-block; + white-space: nowrap; + + &__tooltip { + position: absolute; + top: 50%; + transform: translateY(-50%); + left: -3px; + margin-top: -1px; + background: $euiColorEmptyShade; + border-radius: 2px; + width: calc(100% + 4px); + height: calc(100% + 4px); + padding: 0 2px; + display: none; + align-items: center; + box-shadow: 0 1px 3px rgba(black, 0.1); + border: 1px solid $euiBorderColor; + width: auto; + white-space: nowrap; + + .truncated-content:hover & { + display: flex; + } + } +} diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/truncate/truncated_content.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/truncate/truncated_content.tsx new file mode 100644 index 0000000000000..7785f75b71d34 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/shared/truncate/truncated_content.tsx @@ -0,0 +1,35 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; + +import { truncate, truncateBeginning } from './'; + +import './truncated_content.scss'; + +interface ITruncatedContentProps { + content: string; + length: number; + beginning?: boolean; + tooltipType?: 'inline' | 'title'; +} + +export const TruncatedContent: React.FC = ({ + content, + length, + beginning = false, + tooltipType = 'inline', +}) => { + if (content.length <= length) return <>{content}; + + const inline = tooltipType === 'inline'; + return ( + + {beginning ? truncateBeginning(content, length) : truncate(content, length)} + {inline && {content}} + + ); +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/use_did_update_effect/index.ts b/x-pack/plugins/enterprise_search/public/applications/shared/use_did_update_effect/index.ts new file mode 100644 index 0000000000000..05c60ebced088 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/shared/use_did_update_effect/index.ts @@ -0,0 +1,7 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export { useDidUpdateEffect } from './use_did_update_effect'; diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/use_did_update_effect/use_did_update_effect.test.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/use_did_update_effect/use_did_update_effect.test.tsx new file mode 100644 index 0000000000000..e3d2ffb44f01e --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/shared/use_did_update_effect/use_did_update_effect.test.tsx @@ -0,0 +1,33 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { useState } from 'react'; +import { mount } from 'enzyme'; + +import { EuiLink } from '@elastic/eui'; + +import { useDidUpdateEffect } from './use_did_update_effect'; + +const fn = jest.fn(); + +const TestHook = ({ value }: { value: number }) => { + const [inputValue, setValue] = useState(value); + useDidUpdateEffect(fn, [inputValue]); + return setValue(2)} />; +}; + +const wrapper = mount(); + +describe('useDidUpdateEffect', () => { + it('should not fire function when value unchanged', () => { + expect(fn).not.toHaveBeenCalled(); + }); + + it('should fire function when value changed', () => { + wrapper.find(EuiLink).simulate('click'); + expect(fn).toHaveBeenCalled(); + }); +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/use_did_update_effect/use_did_update_effect.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/use_did_update_effect/use_did_update_effect.tsx new file mode 100644 index 0000000000000..4c3e10fc84b84 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/shared/use_did_update_effect/use_did_update_effect.tsx @@ -0,0 +1,23 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +/* + * Sometimes we don't want to fire the initial useEffect call. + * This custom Hook only fires after the intial render has completed. + */ +import { useEffect, useRef, DependencyList } from 'react'; + +export const useDidUpdateEffect = (fn: Function, inputs: DependencyList) => { + const didMountRef = useRef(false); + + useEffect(() => { + if (didMountRef.current) { + fn(); + } else { + didMountRef.current = true; + } + }, inputs); +};