-
Notifications
You must be signed in to change notification settings - Fork 69
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 10 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,63 @@ | ||
import React, { useState } from 'react'; | ||
import { DataQueryFilter, useLazyDataSource, useUuiContext } from '@epam/uui-core'; | ||
import { FlexCell, PickerInput, PickerTogglerTag, Tooltip } from '@epam/uui'; | ||
import { Location } from '@epam/uui-docs'; | ||
|
||
const cascadeSelectionModes: Array<{ id: 'explicit' | 'implicit'; caption: string }> = [ | ||
{ | ||
id: 'explicit', | ||
caption: 'Explicit cascade selection', | ||
}, { | ||
id: 'implicit', | ||
caption: 'Implicit cascade selection', | ||
}, | ||
]; | ||
|
||
export default function PickerTogglerTagDemoExample() { | ||
const svc = useUuiContext(); | ||
const [value, onValueChange] = useState<string[]>(); | ||
const [cascadeSelection] = useState(cascadeSelectionModes[0].id); | ||
|
||
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, | ||
}, | ||
[], | ||
); | ||
|
||
return ( | ||
<FlexCell width={ 300 }> | ||
<PickerInput | ||
dataSource={ dataSource } | ||
value={ value } | ||
onValueChange={ onValueChange } | ||
entityName="location" | ||
selectionMode="multi" | ||
valueType="id" | ||
cascadeSelection={ cascadeSelection } | ||
maxItems={ 2 } | ||
renderTag={ (props) => { | ||
if (props.isCollapsed) { | ||
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 add comment that it renders 'N items selected' tag 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 |
||
return ( | ||
<PickerTogglerTag { ...props } key="selected" /> | ||
); | ||
} else { | ||
return ( | ||
<Tooltip key={ props.rowProps?.id } content={ `${props.rowProps?.path.map((i) => i.value.name).join('/')}/${props.caption}` }> | ||
<PickerTogglerTag { ...props } /> | ||
</Tooltip> | ||
); | ||
} | ||
} } | ||
/> | ||
</FlexCell> | ||
); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -80,6 +80,7 @@ export class PickerInputDoc extends BaseDocsBlock { | |
<DocExample title="Picker with changed array of items" path="./_examples/pickerInput/PickerWithChangingItemsArray.example.tsx" /> | ||
<DocExample title="Linked pickers" path="./_examples/pickerInput/LinkedPickers.example.tsx" /> | ||
<DocExample title="Change portal target and dropdown placement" path="./_examples/pickerInput/ConfigurePortalTargetAndPlacement.example.tsx" /> | ||
<DocExample title="PickerTogglerTag and renderTag demo" path="./_examples/pickerInput/PickerTogglerTagDemo.example.tsx" /> | ||
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. Change title to 'Custom toggler tag render' 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 |
||
</> | ||
); | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
[ | ||
{ | ||
"type": "paragraph", | ||
"children": [ | ||
{ | ||
"text": "In this example, we use " | ||
}, | ||
{ | ||
"text": "renderTag", | ||
"uui-richTextEditor-code": true | ||
}, | ||
{ | ||
"text": " callback to make custom Tags with Tooltip if they are not a Tag for collapsed values. " | ||
}, | ||
{ | ||
"text": "PickerTogglerTag", | ||
"uui-richTextEditor-code": true | ||
}, | ||
{ | ||
"text": " is a component we recommend to use for rendering Tags." | ||
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 change this here text to - You can utilize the default UUI implementation of tags for PickerInput by using the PickerTogglerTag component. 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 |
||
} | ||
] | ||
} | ||
] |
Large diffs are not rendered by default.
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -10,7 +10,7 @@ export const i18n = { | |
showAll: 'SHOW ALL', | ||
}, | ||
pickerToggler: { | ||
createItemValue: (length: number, entityName: string) => `${length} ${entityName} selected`, | ||
createCollapsedName: (length: number, entityName: string) => `+ ${length} ${entityName} selected`, | ||
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't rename this object fields since it will cause the breaking change, let's revert 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. fixed |
||
}, | ||
pickerInput: { | ||
defaultPlaceholder: (entity: string) => `Please select ${entity}`, | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,9 +1,8 @@ | ||
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 } 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 PickerTogglerProps<TItem = any, TId = any> | ||
|
@@ -37,7 +36,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 +103,33 @@ 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?.(); | ||
|
||
const multiItems = props.selection?.map((row) => { | ||
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 rename this variable to 'tags', to be more informative 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 |
||
const newMultiItems = { | ||
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 rename this variable to 'tagProps', to be more informative 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 |
||
...row, | ||
rowProps: row, | ||
caption: props.getName(row.value), | ||
isCollapsed: false, | ||
isDisabled: row.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?.(newMultiItems); | ||
}); | ||
|
||
if (props.selectedRowsCount > maxItems) { | ||
const collapsedItem = props.renderItem?.({ | ||
caption: i18n.pickerToggler.createCollapsedName(props.selectedRowsCount - maxItems, props.entityName || ''), | ||
isCollapsed: true, | ||
isDisabled: false, | ||
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); | ||
}); | ||
multiItems.push(collapsedItem); | ||
} | ||
|
||
return multiItems; | ||
}; | ||
|
||
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 making custom Tags. | ||
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 change to - 'Render callback for picker toggler selection tag' 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 |
||
* If omitted, they will be rendered by default component PickerTogglerTag. | ||
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.
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 |
||
*/ | ||
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 = () => { | ||
|
@@ -66,7 +73,12 @@ function PickerInputComponent<TItem, TId>({ highlightSearchMatches = true, ...pr | |
}; | ||
|
||
const renderTarget = (targetProps: IDropdownToggler & PickerTogglerProps<TItem, TId>) => { | ||
const renderTargetFn = props.renderToggler || ((props) => <PickerToggler { ...props } />); | ||
const renderTargetFn = props.renderToggler || ((pickerTogglerProps) => ( | ||
<PickerToggler | ||
{ ...pickerTogglerProps } | ||
renderItem={ props.renderTag ? (renderItemProps) => props.renderTag(renderItemProps) : null } | ||
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. Can we add 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 |
||
/> | ||
)); | ||
|
||
return ( | ||
<IEditableDebouncer | ||
|
@@ -106,7 +118,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 +142,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) } | ||
/> | ||
); | ||
}; | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,21 +1,18 @@ | ||
import * as React from 'react'; | ||
import { DataRowProps } from '@epam/uui-core'; | ||
import { PickerToggler as UuiPickerToggler, PickerTogglerProps } from '@epam/uui-components'; | ||
import { TextPlaceholder } from '../typography'; | ||
import { systemIcons } from '../../icons/icons'; | ||
import { Tag } from '../widgets'; | ||
import * as types from '../types'; | ||
import { getMaxItems } from './helpers'; | ||
import { PickerToggler as UuiPickerToggler, PickerTogglerProps } from '@epam/uui-components'; | ||
import { PickerTogglerTag, PickerTogglerTagProps } from './PickerTogglerTag'; | ||
import css from './PickerToggler.module.scss'; | ||
import { systemIcons } from '../../icons/icons'; | ||
|
||
const defaultSize = '36'; | ||
const defaultMode = types.EditMode.FORM; | ||
|
||
export interface PickerTogglerMods extends types.IHasEditMode { | ||
/** | ||
* Defines component size | ||
* @default 36 | ||
*/ | ||
* Defines component size | ||
* @default 36 | ||
*/ | ||
size?: '24' | '30' | '36' | '42' | '48'; | ||
} | ||
|
||
|
@@ -27,44 +24,14 @@ function applyPickerTogglerMods(mods: PickerTogglerMods) { | |
]; | ||
} | ||
|
||
function PickerTogglerComponent<TItem extends string, TId>(props: PickerTogglerProps<TItem, TId> & PickerTogglerMods, ref: React.ForwardedRef<HTMLElement>) { | ||
const getPickerTogglerButtonSize = (propSize: types.ControlSize) => { | ||
switch (propSize) { | ||
case '48': | ||
return '42'; | ||
case '42': | ||
return '36'; | ||
case '36': | ||
return '30'; | ||
case '30': | ||
return '24'; | ||
case '24': | ||
return '18'; | ||
} | ||
}; | ||
|
||
const getCaption = (row: DataRowProps<TItem, TId>) => { | ||
const maxItems = getMaxItems(props.maxItems); | ||
|
||
if (row.isLoading) { | ||
return <TextPlaceholder />; | ||
} else if (!props.getName || props.selectedRowsCount > maxItems) { | ||
return row.value; | ||
} else { | ||
return props.getName(row.value); | ||
} | ||
}; | ||
|
||
const renderItem = (row: DataRowProps<TItem, TId>) => ( | ||
<Tag | ||
key={ row.rowKey } | ||
caption={ getCaption(row) } | ||
tabIndex={ -1 } | ||
size={ props.size ? getPickerTogglerButtonSize(props.size) : '30' } | ||
onClear={ () => { | ||
row.onCheck?.(row); | ||
} } | ||
isDisabled={ props.isDisabled || props.isReadonly || row?.checkbox?.isDisabled } | ||
function PickerTogglerComponent<TItem extends string, TId>(props: PickerTogglerProps<TItem, TId> & PickerTogglerMods, ref: React.ForwardedRef<HTMLElement>): JSX.Element { | ||
const renderItem = (itemProps: PickerTogglerTagProps<TItem, TId>) => ( | ||
<PickerTogglerTag | ||
{ ...itemProps } | ||
rowProps={ itemProps.rowProps } | ||
key={ itemProps.isCollapsed ? 'collapsed' : itemProps.rowProps?.id as string } | ||
size={ props.size } | ||
isDisabled={ props.isDisabled || props.isReadonly || itemProps.isDisabled } | ||
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 supposed that we shouldn't make any props calculation here, we should receive final prop from params and here just spread it to the component, i believe that default renderItem callback should look like:
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 left only size here, or do we want to move it to the uui-components? |
||
/> | ||
); | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
import * as React from 'react'; | ||
import * as types from '../types'; | ||
import { Tag, TagProps } from '../widgets'; | ||
import { DataRowProps } from '@epam/uui-core'; | ||
|
||
export interface PickerTogglerTagProps<TItem, TId> extends TagProps { | ||
/** Defines component size */ | ||
size?: types.ControlSize; | ||
/** If this is true, then the PickerTogglerTag will be an additional tag with the number of collapsed elements in the caption. */ | ||
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. /** Indicates that tag is collapsed rest selected items, like '+N items selected' */ 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 |
||
isCollapsed?: boolean; | ||
/** Defines row props (see more: uui-components/src/pickers/PickerToggler.tsx PickerTogglerProps<TItem = any, TId = any>) */ | ||
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. /** DataRowProps object of the rendered item. */ 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 |
||
rowProps?: DataRowProps<TItem, TId>; | ||
} | ||
|
||
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.
Why do we have cascadeSelection options in this example?
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.
removed it, it will work by default