-
Notifications
You must be signed in to change notification settings - Fork 9
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: add DefinitionList #143
Changes from 8 commits
88795ae
b167b65
22ffc56
2358393
f2bd7d7
017aa42
1846524
3e92655
056239b
efbc979
6add9ed
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,106 @@ | ||
@use '../variables'; | ||
@use '@gravity-ui/uikit/styles/mixins'; | ||
|
||
$block: '.#{variables.$ns}definition-list'; | ||
|
||
#{$block} { | ||
$class: &; | ||
|
||
margin: 0; | ||
|
||
&__item { | ||
display: flex; | ||
align-items: baseline; | ||
gap: var(--g-spacing-1); | ||
|
||
& + & { | ||
margin-block-start: var(--g-spacing-4); | ||
} | ||
} | ||
|
||
&__term-container { | ||
flex: 0 0 300px; | ||
display: flex; | ||
align-items: baseline; | ||
|
||
overflow: hidden; | ||
position: relative; | ||
} | ||
|
||
&__term-wrapper { | ||
white-space: nowrap; | ||
overflow: hidden; | ||
text-overflow: ellipsis; | ||
|
||
flex: 0 1 auto; | ||
color: var(--g-color-text-secondary); | ||
|
||
position: relative; | ||
} | ||
|
||
&__term-container_multiline &__term-wrapper { | ||
white-space: unset; | ||
} | ||
|
||
&__term-container_multiline &__item-note-tooltip { | ||
position: absolute; | ||
} | ||
|
||
&__dots { | ||
box-sizing: border-box; | ||
flex: 1 0 auto; | ||
min-width: 40px; | ||
margin: 0 2px; | ||
border-block-end: 1px dotted var(--g-color-line-generic-active); | ||
} | ||
|
||
&__dots_with-note { | ||
margin-inline-start: 15px; | ||
min-width: 25px; | ||
} | ||
|
||
&__definition { | ||
flex: 0 1 auto; | ||
margin: 0; | ||
} | ||
|
||
&_responsive { | ||
#{$block}__term-container { | ||
flex: 1 0 auto; | ||
} | ||
} | ||
|
||
&__copy-container { | ||
position: relative; | ||
display: inline-flex; | ||
padding-inline-end: var(--g-spacing-7); | ||
|
||
margin-inline-end: calc(-1 * var(--g-spacing-7)); | ||
|
||
&:hover { | ||
#{$block}__copy-button { | ||
opacity: 1; | ||
} | ||
} | ||
} | ||
|
||
&__copy-container_icon-inside { | ||
padding-inline-end: unset; | ||
margin-inline-end: unset; | ||
|
||
#{$block}__copy-button { | ||
inset-block-start: 0; | ||
} | ||
} | ||
|
||
&__copy-button { | ||
position: absolute; | ||
display: inline-block; | ||
inset-inline-end: 0; | ||
margin-inline-start: 10px; | ||
opacity: 0; | ||
&:focus-visible { | ||
opacity: 1; | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,149 @@ | ||
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; | ||
amje marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
export interface DefinitionListItem { | ||
name: string; | ||
key?: string; | ||
href?: string; | ||
content?: React.ReactNode; | ||
title?: string; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe split into 2 props, for name and for content? |
||
copyText?: string; | ||
note?: DefinitionListItemNote; | ||
multilineName?: boolean; | ||
} | ||
|
||
export interface DefinitionListProps { | ||
items: DefinitionListItem[]; | ||
copyPosition?: 'inside' | 'outside'; | ||
responsive?: boolean; | ||
nameMaxWidth?: number; | ||
contentMaxWidth?: 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; | ||
const popoverClassName = b('item-note-tooltip'); | ||
if (note) { | ||
if (typeof note === 'string') { | ||
noteElement = ( | ||
<HelpPopover | ||
className={popoverClassName} | ||
htmlContent={note} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Let's use normal content props, not html |
||
placement={['bottom', 'top']} | ||
/> | ||
); | ||
} | ||
|
||
if (typeof note === 'object') { | ||
noteElement = <HelpPopover className={popoverClassName} {...note} />; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Let's also set default placement here There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why do you think we need it? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Then why we set placement when note is a string? |
||
} | ||
} | ||
return noteElement; | ||
} | ||
|
||
export function DefinitionList({ | ||
items, | ||
responsive, | ||
nameMaxWidth, | ||
contentMaxWidth = 'auto', | ||
className, | ||
itemClassName, | ||
copyPosition = 'outside', | ||
}: DefinitionListProps) { | ||
const keyStyle = nameMaxWidth | ||
? { | ||
flexBasis: nameMaxWidth, | ||
} | ||
: {}; | ||
|
||
const valueStyle = | ||
typeof contentMaxWidth === 'number' | ||
? { | ||
flexBasis: contentMaxWidth, | ||
maxWidth: contentMaxWidth, | ||
} | ||
: {}; | ||
return ( | ||
<dl className={b({responsive}, className)} role="list"> | ||
amje marked this conversation as resolved.
Show resolved
Hide resolved
|
||
{items.map(({name, href, content, title, copyText, note, key, multilineName}) => { | ||
const term = href ? <Link href={href}>{name}</Link> : name; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe instead of having There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Like it! But in this case There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We can generate them by ourselves |
||
const definitionContent = content ?? '—'; | ||
const iconInside = copyPosition === 'inside'; | ||
const definition = copyText ? ( | ||
<div className={b('copy-container', {'icon-inside': iconInside})}> | ||
<span>{definitionContent}</span> | ||
<ClipboardButton | ||
size="s" | ||
text={copyText} | ||
className={b('copy-button')} | ||
view={iconInside ? 'raised' : 'flat-secondary'} | ||
/> | ||
</div> | ||
) : ( | ||
definitionContent | ||
); | ||
const noteElement = ( | ||
<React.Fragment> | ||
| ||
{getNoteElement(note)} | ||
</React.Fragment> | ||
); | ||
return ( | ||
<div key={key ?? name} className={b('item', itemClassName)}> | ||
amje marked this conversation as resolved.
Show resolved
Hide resolved
|
||
<dt | ||
className={b('term-container', {multiline: multilineName})} | ||
style={keyStyle} | ||
> | ||
<div className={b('term-wrapper')}> | ||
{term} | ||
{multilineName && noteElement} | ||
</div> | ||
{!multilineName && noteElement} | ||
<div className={b('dots', {'with-note': Boolean(note)})} /> | ||
</dt> | ||
<dd | ||
className={b('definition')} | ||
title={getTitle(title, content)} | ||
style={{ | ||
...valueStyle, | ||
lineBreak: | ||
typeof content === 'string' && isUnbreakableOver(20)(content) | ||
? 'anywhere' | ||
: undefined, | ||
}} | ||
> | ||
{definition} | ||
</dd> | ||
</div> | ||
); | ||
})} | ||
</dl> | ||
); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
## 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 | | ||
| nameMaxWidth | `number` | | | Maximum width of term | | ||
| contentMaxWidth | `number \| 'auto'` | | 'auto' | Maximum width of definition | | ||
| className | `string` | | | Class name for the list container | | ||
| itemClassName | `string` | | | Class name for the list item | | ||
| copyPosition | `'inside' \| 'outside'` | 'outside' | | If set to `inside`, copy icon will be placed over definition | | ||
|
||
#### 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 | | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. String with lower "s" |
||
| 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', | ||
}, | ||
{name: 'Empty value with copy', copyText: 'nothing to copy'}, | ||
]} | ||
nameMaxWidth="100" | ||
contentMaxWidth="100" | ||
/> | ||
``` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This works without
~
correctly?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This syntax is used all over in the repo. No problem.