-
Notifications
You must be signed in to change notification settings - Fork 68
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
PickerTogglerTag#2008 #2087
PickerTogglerTag#2008 #2087
Changes from 14 commits
de21b86
8105bcb
0f20602
0cd6d1a
e82dff9
1535bea
e74fe08
ab89517
898f9ef
3a38dd9
8cf9d8c
34ad4a1
2b83528
1fb50c7
2ff7675
ba8da41
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,55 @@ | ||
import React, { useState } from 'react'; | ||
import { DataQueryFilter, useLazyDataSource, useUuiContext } from '@epam/uui-core'; | ||
import { FlexCell, PickerInput, PickerTogglerTag, PickerTogglerTagProps, Tooltip } from '@epam/uui'; | ||
import { Location } from '@epam/uui-docs'; | ||
|
||
export default function PickerTogglerTagDemoExample() { | ||
const svc = useUuiContext(); | ||
const [value, onValueChange] = useState<string[]>(['225284', '2747351', '3119841', '3119746']); | ||
|
||
const dataSource = useLazyDataSource<Location, string, DataQueryFilter<Location>>( | ||
{ | ||
api: (request, ctx) => { | ||
const { search } = request; | ||
// and since parentId is meaningful value, it is required to exclude it from the filter. | ||
const filter = search ? {} : { parentId: ctx?.parentId }; | ||
return svc.api.demo.locations({ ...request, search, filter }); | ||
}, | ||
getId: (i) => i.id, | ||
getParentId: (i) => i.parentId, | ||
getChildCount: (l) => l.childCount, | ||
}, | ||
[], | ||
); | ||
|
||
const renderTag = (props: PickerTogglerTagProps<Location, string>) => { | ||
if (props.isCollapsed) { | ||
// rendering '+ N items selected' Tag | ||
return ( | ||
<PickerTogglerTag { ...props } key="selected" /> | ||
); | ||
} else { | ||
// rendering all other Tags with Tooltip | ||
return ( | ||
<Tooltip key={ props.rowProps?.id } content={ `${props.rowProps?.value?.tz}/${props.caption}` }> | ||
<PickerTogglerTag { ...props } /> | ||
</Tooltip> | ||
); | ||
} | ||
}; | ||
|
||
return ( | ||
<FlexCell width={ 300 }> | ||
<PickerInput | ||
dataSource={ dataSource } | ||
value={ value } | ||
onValueChange={ onValueChange } | ||
entityName="location" | ||
selectionMode="multi" | ||
valueType="id" | ||
maxItems={ 2 } | ||
renderTag={ (props) => renderTag(props) } | ||
/> | ||
</FlexCell> | ||
); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
[ | ||
{ | ||
"type": "paragraph", | ||
"children": [ | ||
{ | ||
"text": "You can utilize the default UUI implementation of tags for PickerInput by using the " | ||
}, | ||
{ | ||
"text": "PickerTogglerTag", | ||
"uui-richTextEditor-code": true | ||
}, | ||
{ | ||
"text": " component." | ||
} | ||
] | ||
} | ||
] |
Large diffs are not rendered by default.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,17 +1,27 @@ | ||
import * as React from 'react'; | ||
import { IPickerToggler, IHasIcon, IHasCX, ICanBeReadonly, Icon, uuiMod, uuiElement, uuiMarkers, DataRowProps, cx, IHasRawProps, ICanFocus, isEventTargetInsideClickable } from '@epam/uui-core'; | ||
import { IPickerToggler, IHasIcon, IHasCX, ICanBeReadonly, Icon, uuiMod, uuiElement, uuiMarkers, cx, IHasRawProps, ICanFocus, isEventTargetInsideClickable, DataRowProps, IHasCaption, IDisableable } from '@epam/uui-core'; | ||
import { IconContainer } from '../layout'; | ||
import css from './PickerToggler.module.scss'; | ||
import { i18n } from '../i18n'; | ||
import { useCallback } from 'react'; | ||
import { getMaxItems } from './helpers'; | ||
|
||
export interface IRenderItemProps<TItem, TId> extends IHasCaption, IDisableable { | ||
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. Lets rename to PickerTogglerRenderItemParams 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. done |
||
/** Key for the component */ | ||
key: string; | ||
/** DataRowProps object of the rendered item */ | ||
rowProps?: DataRowProps<TItem, TId>; | ||
/** Indicates that tag is collapsed rest selected items, like '+N items selected' */ | ||
isCollapsed?: boolean; | ||
/** Call to clear a value */ | ||
onClear?(e?: any): void; | ||
} | ||
|
||
export interface PickerTogglerProps<TItem = any, TId = any> | ||
extends IPickerToggler<TItem, TId>, ICanFocus<HTMLElement>, IHasIcon, IHasCX, ICanBeReadonly, IHasRawProps<React.HTMLAttributes<HTMLElement>> { | ||
cancelIcon?: Icon; | ||
dropdownIcon?: Icon; | ||
autoFocus?: boolean; | ||
renderItem?(props: DataRowProps<TItem, TId>): React.ReactNode; | ||
renderItem?(props: IRenderItemProps<TItem, TId>): React.ReactNode; | ||
getName?: (item: TItem) => string; | ||
entityName?: string; | ||
maxItems?: number; | ||
|
@@ -37,7 +47,7 @@ function PickerTogglerComponent<TItem, TId>(props: PickerTogglerProps<TItem, TId | |
|
||
React.useImperativeHandle(ref, () => toggleContainer.current, [toggleContainer.current]); | ||
|
||
const handleClick = useCallback( | ||
const handleClick = React.useCallback( | ||
(event: Event) => { | ||
if (props.isInteractedOutside(event) && inFocus) { | ||
blur(); | ||
|
@@ -104,26 +114,37 @@ function PickerTogglerComponent<TItem, TId>(props: PickerTogglerProps<TItem, TId | |
|
||
const renderItems = () => { | ||
const maxItems = getMaxItems(props.maxItems); | ||
if (props.selectedRowsCount > maxItems) { | ||
return props.renderItem?.({ | ||
value: i18n.pickerToggler.createItemValue(props.selectedRowsCount, props.entityName || ''), | ||
onCheck: () => { | ||
props.onClear?.(); | ||
let isDisabled = props.isDisabled || props.isReadonly; | ||
|
||
const tags = props.selection?.map((row) => { | ||
isDisabled = isDisabled || row.isDisabled; | ||
|
||
const tagProps = { | ||
key: row?.id as string, | ||
rowProps: row, | ||
caption: props.getName(row.value), | ||
isCollapsed: false, | ||
isDisabled, | ||
onClear: () => { | ||
row.onCheck?.(row); | ||
// When we delete item it disappears from the DOM and focus is passed to the Body. So in this case we have to return focus on the toggleContainer by hand. | ||
toggleContainer.current?.focus(); | ||
}, | ||
} }; | ||
return props.renderItem?.(tagProps); | ||
}); | ||
|
||
if (props.selectedRowsCount > maxItems) { | ||
const collapsedTagProps = props.renderItem?.({ | ||
key: 'collapsed', | ||
caption: i18n.pickerToggler.createItemValue(props.selectedRowsCount - maxItems, props.entityName || ''), | ||
isCollapsed: true, | ||
isDisabled, | ||
onClear: null, | ||
} as any); | ||
} else { | ||
return props.selection?.map((row) => { | ||
const newRow = { ...row, | ||
onCheck: () => { | ||
row.onCheck?.(row); | ||
// When we delete item it disappears from the DOM and focus is passed to the Body. So in this case we have to return focus on the toggleContainer by hand. | ||
toggleContainer.current?.focus(); | ||
} }; | ||
return props.renderItem?.(newRow); | ||
}); | ||
tags.push(collapsedTagProps); | ||
} | ||
|
||
return tags; | ||
}; | ||
|
||
const renderInput = () => { | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -10,11 +10,18 @@ import { DataPickerBody } from './DataPickerBody'; | |
import { DataPickerRow } from './DataPickerRow'; | ||
import { DataPickerFooter } from './DataPickerFooter'; | ||
import { PickerItem } from './PickerItem'; | ||
import { PickerTogglerTagProps } from './PickerTogglerTag'; | ||
|
||
const pickerHeight = 300; | ||
const pickerWidth = 360; | ||
|
||
export type PickerInputProps<TItem, TId> = SizeMod & IHasEditMode & PickerInputBaseProps<TItem, TId>; | ||
export type PickerInputProps<TItem, TId> = SizeMod & IHasEditMode & PickerInputBaseProps<TItem, TId> & { | ||
/** | ||
* Render callback for picker toggler selection tag | ||
* If omitted, default `PickerTogglerTag` component will be rendered | ||
*/ | ||
renderTag?: (props: PickerTogglerTagProps<TItem, TId>) => JSX.Element; | ||
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. I believe this callback receive IRenderItemProps interface, not PickerTogglerTagProps, yes? 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. fixed |
||
}; | ||
|
||
function PickerInputComponent<TItem, TId>({ highlightSearchMatches = true, ...props }: PickerInputProps<TItem, TId>, ref: React.ForwardedRef<HTMLElement>) { | ||
const toggleModalOpening = () => { | ||
|
@@ -106,7 +113,7 @@ function PickerInputComponent<TItem, TId>({ highlightSearchMatches = true, ...pr | |
.join(' / '); | ||
}; | ||
|
||
const renderItem = (item: TItem, rowProps: DataRowProps<TItem, TId>, dsState: DataSourceState) => { | ||
const renderRowItem = (item: TItem, rowProps: DataRowProps<TItem, TId>, dsState: DataSourceState) => { | ||
const { flattenSearchResults } = view.getConfig(); | ||
|
||
return ( | ||
|
@@ -130,7 +137,7 @@ function PickerInputComponent<TItem, TId>({ highlightSearchMatches = true, ...pr | |
key={ rowProps.rowKey } | ||
size={ getRowSize() } | ||
padding={ props.editMode === 'modal' ? '24' : '12' } | ||
renderItem={ (item, itemProps) => renderItem(item, itemProps, dsState) } | ||
renderItem={ (item, itemProps) => renderRowItem(item, itemProps, dsState) } | ||
/> | ||
); | ||
}; | ||
|
@@ -172,12 +179,13 @@ function PickerInputComponent<TItem, TId>({ highlightSearchMatches = true, ...pr | |
}; | ||
|
||
const rows = getRows(); | ||
const renderItem = props.renderTag ? props.renderTag : null; | ||
|
||
return ( | ||
<Dropdown | ||
renderTarget={ (dropdownProps) => { | ||
const targetProps = getTogglerProps(); | ||
return renderTarget({ ...dropdownProps, ...targetProps }); | ||
return renderTarget({ ...dropdownProps, ...targetProps, renderItem }); | ||
} } | ||
renderBody={ (bodyProps) => renderBody({ ...bodyProps, ...getPickerBodyProps(rows), ...getListProps() }, rows) } | ||
value={ shouldShowBody() } | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
import * as React from 'react'; | ||
import * as types from '../types'; | ||
import { Tag, TagProps } from '../widgets'; | ||
import { IRenderItemProps } from '@epam/uui-components'; | ||
|
||
export interface PickerTogglerTagProps<TItem, TId> extends IRenderItemProps<TItem, TId>, TagProps { | ||
/** Defines component size */ | ||
size?: types.ControlSize; | ||
} | ||
|
||
const getPickerTogglerButtonSize = (propSize?: types.ControlSize):TagProps['size'] => { | ||
switch (propSize) { | ||
case '48': | ||
return '42'; | ||
case '42': | ||
return '36'; | ||
case '36': | ||
return '30'; | ||
case '30': | ||
return '24'; | ||
case '24': | ||
return '18'; | ||
default: | ||
return '30'; | ||
} | ||
}; | ||
|
||
export const PickerTogglerTag = React.forwardRef((props: PickerTogglerTagProps<any, any>, ref: React.Ref<HTMLElement>) => { | ||
const tagProps = { | ||
...props, | ||
tabIndex: -1, | ||
size: getPickerTogglerButtonSize(props.size), | ||
}; | ||
|
||
return <Tag ref={ ref } { ...tagProps } />; | ||
}); |
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.
Add description that user should use renderTag prop for such cunstomization
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.
done