Skip to content

Commit

Permalink
feat: add DefinitionList
Browse files Browse the repository at this point in the history
  • Loading branch information
Raubzeug committed Feb 6, 2024
1 parent e035a53 commit c77a634
Show file tree
Hide file tree
Showing 10 changed files with 532 additions and 0 deletions.
1 change: 1 addition & 0 deletions CODEOWNERS
Validating CODEOWNERS rules …
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
* @amje @ValeraS @korvin89
/src/components/DefinitionList @Raubzeug
/src/components/FilePreview @KirillDyachkovskiy
/src/components/FormRow @ogonkov
/src/components/HelpPopover @Raubzeug
Expand Down
102 changes: 102 additions & 0 deletions src/components/DefinitionList/DefinitionList.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
@use '../variables';
@use '@gravity-ui/uikit/styles/mixins';

$block: '.#{variables.$ns}definition-list';

#{$block} {
$class: &;

margin: 0;

&__item {
display: flex;
align-items: baseline;
gap: 4px;

& + & {
margin-top: 16px;
}
}

&__item-note-tooltip {
margin-right: 4px;
}

&__term-container {
flex: 0 0 300px;
display: flex;
align-items: baseline;

overflow: hidden;
}

&__term {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;

flex: 0 1 auto;
color: var(--g-color-text-secondary);

&_multiline {
white-space: unset;
}
}

&__dots {
box-sizing: border-box;
flex: 1 0 auto;
min-width: 40px;
margin: 0 2px;
border-bottom: 1px dashed var(--g-color-text-secondary);
}

&__definition {
flex: 0 1 auto;
margin: 0;
}

&_responsive {
#{$block}__term-container {
flex: 1 0 auto;
}
}

&__copy-container {
position: relative;
display: inline-flex;
align-items: center;
padding-right: variables.$normalOffset;

margin-right: -(variables.$normalOffset);

&:hover {
#{$block}__copy-button {
visibility: visible;
}
}
}

&__copy-container_icon-inside {
padding-right: unset;
margin-right: unset;

#{$block}__copy-button {
top: 0;
background-color: var(--g-color-base-background);
}
}

&__copy-button {
position: absolute;
display: inline-block;
right: 0;
margin-left: 10px;
visibility: hidden;
color: var(--g-color-text-secondary);

&:hover {
color: var(--g-color-text-primary);
}
}
}
155 changes: 155 additions & 0 deletions src/components/DefinitionList/DefinitionList.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
import React from 'react';

import {ClipboardButton, Link} from '@gravity-ui/uikit';

import {HelpPopover} from '../HelpPopover';
import type {HelpPopoverProps} from '../HelpPopover';
import {block} from '../utils/cn';

import {isUnbreakableOver} from './utils';

import './DefinitionList.scss';

type DefinitionListItemNote = string | HelpPopoverProps;

export interface DefinitionListItem {
name: string;
key?: string;
href?: string;
content?: React.ReactNode;
title?: string;
copyText?: string;
copyIconOver?: boolean;
note?: DefinitionListItemNote;
multilineName?: boolean;
}

export interface DefinitionListProps {
items: DefinitionListItem[];
responsive?: boolean;
keyMaxWidth?: number;
valueMaxWidth?: number | 'auto';
className?: string;
itemClassName?: string;
}

export const b = block('definition-list');

function getTitle(title?: string, content?: React.ReactNode) {
if (title) {
return title;
}

if (typeof content === 'string' || typeof content === 'number') {
return String(content);
}

return undefined;
}

function getNoteElement(note?: DefinitionListItemNote) {
let noteElement = null;
if (note) {
if (typeof note === 'string') {
noteElement = (
<HelpPopover
className={b('item-note-tooltip')}
tooltipContentClassName="yfm"
htmlContent={note}
placement={['bottom', 'top']}
offset={{left: 4}}
/>
);
}

if (typeof note === 'object') {
noteElement = <HelpPopover offset={{left: 4}} {...note} />;
}
}
return noteElement;
}

export function DefinitionList({
items,
responsive,
keyMaxWidth,
valueMaxWidth = 'auto',
className,
itemClassName,
}: DefinitionListProps) {
const keyStyle = keyMaxWidth
? {
flexBasis: keyMaxWidth,
}
: {};

const valueStyle =
typeof valueMaxWidth === 'number'
? {
flexBasis: valueMaxWidth,
maxWidth: valueMaxWidth,
}
: {};
return (
<dl className={b({responsive}, className)} role="list">
{items.map(
({
name,
href,
content,
title,
copyText,
note,
key,
copyIconOver,
multilineName,
}) => {
const term = href ? <Link href={href}>{name}</Link> : name;
const definitionContent = content ?? '—';
const definition = copyText ? (
<div className={b('copy-container', {'icon-inside': copyIconOver})}>
<span>{definitionContent}</span>
<ClipboardButton
size={14}
text={copyText}
className={b('copy-button')}
/>
</div>
) : (
definitionContent
);
return (
<div key={key ?? name} className={b('item', itemClassName)} role="listitem">
<div className={b('term-container')} style={keyStyle}>
<dt
className={b('term', {multiline: multilineName})}
title={name}
role="term"
>
{term}
</dt>
{getNoteElement(note)}
<div className={b('dots')} />
</div>
<dd
role="definition"
className={b('definition')}
title={getTitle(title, content)}
style={{
...valueStyle,
lineBreak:
typeof content === 'string' &&
isUnbreakableOver(20)(content)
? 'anywhere'
: undefined,
}}
>
{definition}
</dd>
</div>
);
},
)}
</dl>
);
}
46 changes: 46 additions & 0 deletions src/components/DefinitionList/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
## DefinitionList

The component to display definition list with term and definition separated by dots.

### PropTypes

| Property | Type | Required | Default | Description |
| :-------------- | :--------------------- | :------: | :------ | :------------------------------------------------------- |
| [items](#items) | `DefinitionListItem[]` | yes | | Items of the list |
| responsive | `boolean` | | | If set to `true` list will take 100% width of its parent |
| keyMaxWidth | `number` | | | Maximum width of term |
| valueMaxWidth | `number \| 'auto'` | | 'auto' | Maximum width of definition |
| className | `string` | | | Class name for the list container |
| itemClassName | `string` | | | Class name for the list item |

#### Items

Configuration for list items

| Property | Type | Required | Default | Description |
| ------------- | ---------------------------- | -------- | ------- | -------------------------------------------------------------- |
| name | `String` | true | | Term |
| multilineName | `boolean` | | | If set, term will be multiline |
| key | `String` | | | Item key (if not set, `name` property will be treated as key) |
| href | `String` | | | If set, term will be wrapped with link |
| content | `ReactNode` | | | Definition |
| title | `String` | | | Title for definition. If not set, `content` value will be used |
| copyText | `String` | | | If set, it will be shown icon for copy this text |
| copyIconOver | `boolean` | | | If set, copy icon will be placed over definition |
| note | `string \| HelpPopoverProps` | | | If set, HelpPopover will be shown next to term |

```jsx
<DefinitionList
items={[
{
name: 'Node value with copy',
content: <strong>value with copy</strong>,
copyText: 'value',
copyIconOver,
},
{name: 'Empty value with copy', copyText: 'nothing to copy'},
]}
keyMaxWidth="100"
valueMaxWidth="100"
/>
```
Loading

0 comments on commit c77a634

Please sign in to comment.