@cloud-ru/ds-list 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +8 -0
- package/LICENSE +201 -0
- package/README.md +1413 -0
- package/dist/cjs/components/Items/AccordionItem/AccordionItem.d.ts +4 -0
- package/dist/cjs/components/Items/AccordionItem/AccordionItem.js +37 -0
- package/dist/cjs/components/Items/AccordionItem/index.d.ts +1 -0
- package/dist/cjs/components/Items/AccordionItem/index.js +17 -0
- package/dist/cjs/components/Items/BaseItem/BaseItem.d.ts +24 -0
- package/dist/cjs/components/Items/BaseItem/BaseItem.js +118 -0
- package/dist/cjs/components/Items/BaseItem/constants.d.ts +3 -0
- package/dist/cjs/components/Items/BaseItem/constants.js +8 -0
- package/dist/cjs/components/Items/BaseItem/index.d.ts +1 -0
- package/dist/cjs/components/Items/BaseItem/index.js +17 -0
- package/dist/cjs/components/Items/BaseItem/styles.module.css +312 -0
- package/dist/cjs/components/Items/GroupSelectItem/GroupSelectItem.d.ts +4 -0
- package/dist/cjs/components/Items/GroupSelectItem/GroupSelectItem.js +22 -0
- package/dist/cjs/components/Items/GroupSelectItem/index.d.ts +1 -0
- package/dist/cjs/components/Items/GroupSelectItem/index.js +17 -0
- package/dist/cjs/components/Items/NextListItem/NextListItem.d.ts +2 -0
- package/dist/cjs/components/Items/NextListItem/NextListItem.js +71 -0
- package/dist/cjs/components/Items/NextListItem/constants.d.ts +2 -0
- package/dist/cjs/components/Items/NextListItem/constants.js +13 -0
- package/dist/cjs/components/Items/NextListItem/index.d.ts +1 -0
- package/dist/cjs/components/Items/NextListItem/index.js +17 -0
- package/dist/cjs/components/Items/NextListItem/types.d.ts +4 -0
- package/dist/cjs/components/Items/NextListItem/types.js +2 -0
- package/dist/cjs/components/Items/PinGroupItem/PinGroupItem.d.ts +7 -0
- package/dist/cjs/components/Items/PinGroupItem/PinGroupItem.js +27 -0
- package/dist/cjs/components/Items/PinGroupItem/index.d.ts +1 -0
- package/dist/cjs/components/Items/PinGroupItem/index.js +17 -0
- package/dist/cjs/components/Items/PinGroupItem/styles.module.css +12 -0
- package/dist/cjs/components/Items/SearchItem/SearchItem.d.ts +17 -0
- package/dist/cjs/components/Items/SearchItem/SearchItem.js +28 -0
- package/dist/cjs/components/Items/SearchItem/index.d.ts +1 -0
- package/dist/cjs/components/Items/SearchItem/index.js +17 -0
- package/dist/cjs/components/Items/SearchItem/styles.module.css +13 -0
- package/dist/cjs/components/Items/SimpleItem/components/DragHandle/DragHandle.d.ts +7 -0
- package/dist/cjs/components/Items/SimpleItem/components/DragHandle/DragHandle.js +20 -0
- package/dist/cjs/components/Items/SimpleItem/components/DragHandle/index.d.ts +1 -0
- package/dist/cjs/components/Items/SimpleItem/components/DragHandle/index.js +17 -0
- package/dist/cjs/components/Items/SimpleItem/components/OverlayDragHandle/OverlayDragHandle.d.ts +2 -0
- package/dist/cjs/components/Items/SimpleItem/components/OverlayDragHandle/OverlayDragHandle.js +9 -0
- package/dist/cjs/components/Items/SimpleItem/components/OverlayDragHandle/index.d.ts +1 -0
- package/dist/cjs/components/Items/SimpleItem/components/OverlayDragHandle/index.js +17 -0
- package/dist/cjs/components/Items/SimpleItem/components/SimpleGroupBlock/SimpleGroupBlock.d.ts +19 -0
- package/dist/cjs/components/Items/SimpleItem/components/SimpleGroupBlock/SimpleGroupBlock.js +52 -0
- package/dist/cjs/components/Items/SimpleItem/components/SimpleGroupBlock/index.d.ts +1 -0
- package/dist/cjs/components/Items/SimpleItem/components/SimpleGroupBlock/index.js +17 -0
- package/dist/cjs/components/Items/SimpleItem/components/SimpleGroupBlockOverlay/SimpleGroupBlockOverlay.d.ts +15 -0
- package/dist/cjs/components/Items/SimpleItem/components/SimpleGroupBlockOverlay/SimpleGroupBlockOverlay.js +19 -0
- package/dist/cjs/components/Items/SimpleItem/components/SimpleGroupBlockOverlay/index.d.ts +1 -0
- package/dist/cjs/components/Items/SimpleItem/components/SimpleGroupBlockOverlay/index.js +17 -0
- package/dist/cjs/components/Items/SimpleItem/components/SimpleItem/SimpleItem.d.ts +11 -0
- package/dist/cjs/components/Items/SimpleItem/components/SimpleItem/SimpleItem.js +48 -0
- package/dist/cjs/components/Items/SimpleItem/components/SimpleItem/index.d.ts +1 -0
- package/dist/cjs/components/Items/SimpleItem/components/SimpleItem/index.js +17 -0
- package/dist/cjs/components/Items/SimpleItem/components/SimpleItemOverlay/SimpleItemOverlay.d.ts +15 -0
- package/dist/cjs/components/Items/SimpleItem/components/SimpleItemOverlay/SimpleItemOverlay.js +19 -0
- package/dist/cjs/components/Items/SimpleItem/components/SimpleItemOverlay/index.d.ts +1 -0
- package/dist/cjs/components/Items/SimpleItem/components/SimpleItemOverlay/index.js +17 -0
- package/dist/cjs/components/Items/SimpleItem/components/index.d.ts +4 -0
- package/dist/cjs/components/Items/SimpleItem/components/index.js +20 -0
- package/dist/cjs/components/Items/SimpleItem/constants.d.ts +7 -0
- package/dist/cjs/components/Items/SimpleItem/constants.js +9 -0
- package/dist/cjs/components/Items/SimpleItem/index.d.ts +2 -0
- package/dist/cjs/components/Items/SimpleItem/index.js +18 -0
- package/dist/cjs/components/Items/SimpleItem/styles.module.css +96 -0
- package/dist/cjs/components/Items/SimpleItem/types.d.ts +3 -0
- package/dist/cjs/components/Items/SimpleItem/types.js +2 -0
- package/dist/cjs/components/Items/SimpleItem/utils.d.ts +6 -0
- package/dist/cjs/components/Items/SimpleItem/utils.js +11 -0
- package/dist/cjs/components/Items/hooks.d.ts +23 -0
- package/dist/cjs/components/Items/hooks.js +130 -0
- package/dist/cjs/components/Items/index.d.ts +5 -0
- package/dist/cjs/components/Items/index.js +21 -0
- package/dist/cjs/components/Items/styles.module.css +168 -0
- package/dist/cjs/components/Items/types.d.ts +168 -0
- package/dist/cjs/components/Items/types.js +2 -0
- package/dist/cjs/components/Items/utils.d.ts +52 -0
- package/dist/cjs/components/Items/utils.js +218 -0
- package/dist/cjs/components/Lists/Droplist/DropList.d.ts +21 -0
- package/dist/cjs/components/Lists/Droplist/DropList.js +36 -0
- package/dist/cjs/components/Lists/Droplist/constants.d.ts +8 -0
- package/dist/cjs/components/Lists/Droplist/constants.js +10 -0
- package/dist/cjs/components/Lists/Droplist/index.d.ts +1 -0
- package/dist/cjs/components/Lists/Droplist/index.js +17 -0
- package/dist/cjs/components/Lists/List/List.d.ts +61 -0
- package/dist/cjs/components/Lists/List/List.js +62 -0
- package/dist/cjs/components/Lists/List/index.d.ts +1 -0
- package/dist/cjs/components/Lists/List/index.js +17 -0
- package/dist/cjs/components/Lists/ListPrivate/ListPrivate.d.ts +18 -0
- package/dist/cjs/components/Lists/ListPrivate/ListPrivate.js +301 -0
- package/dist/cjs/components/Lists/ListPrivate/constants.d.ts +7 -0
- package/dist/cjs/components/Lists/ListPrivate/constants.js +19 -0
- package/dist/cjs/components/Lists/ListPrivate/index.d.ts +1 -0
- package/dist/cjs/components/Lists/ListPrivate/index.js +17 -0
- package/dist/cjs/components/Lists/ListPrivate/styles.module.css +80 -0
- package/dist/cjs/components/Lists/contexts/CollapseProvider.d.ts +17 -0
- package/dist/cjs/components/Lists/contexts/CollapseProvider.js +10 -0
- package/dist/cjs/components/Lists/contexts/FocusListProvider.d.ts +9 -0
- package/dist/cjs/components/Lists/contexts/FocusListProvider.js +9 -0
- package/dist/cjs/components/Lists/contexts/NewListProvider.d.ts +33 -0
- package/dist/cjs/components/Lists/contexts/NewListProvider.js +23 -0
- package/dist/cjs/components/Lists/contexts/OpenListProvider.d.ts +6 -0
- package/dist/cjs/components/Lists/contexts/OpenListProvider.js +10 -0
- package/dist/cjs/components/Lists/contexts/SelectionProvider.d.ts +74 -0
- package/dist/cjs/components/Lists/contexts/SelectionProvider.js +90 -0
- package/dist/cjs/components/Lists/contexts/index.d.ts +5 -0
- package/dist/cjs/components/Lists/contexts/index.js +21 -0
- package/dist/cjs/components/Lists/hooks.d.ts +83 -0
- package/dist/cjs/components/Lists/hooks.js +242 -0
- package/dist/cjs/components/Lists/index.d.ts +5 -0
- package/dist/cjs/components/Lists/index.js +21 -0
- package/dist/cjs/components/Lists/styles.module.css +65 -0
- package/dist/cjs/components/Lists/types.d.ts +200 -0
- package/dist/cjs/components/Lists/types.js +2 -0
- package/dist/cjs/components/Lists/utils.d.ts +8 -0
- package/dist/cjs/components/Lists/utils.js +31 -0
- package/dist/cjs/components/index.d.ts +2 -0
- package/dist/cjs/components/index.js +18 -0
- package/dist/cjs/constants.d.ts +48 -0
- package/dist/cjs/constants.js +51 -0
- package/dist/cjs/helperComponents/CollapseBlockPrivate/CollapseBlockPrivate.d.ts +9 -0
- package/dist/cjs/helperComponents/CollapseBlockPrivate/CollapseBlockPrivate.js +15 -0
- package/dist/cjs/helperComponents/CollapseBlockPrivate/index.d.ts +1 -0
- package/dist/cjs/helperComponents/CollapseBlockPrivate/index.js +17 -0
- package/dist/cjs/helperComponents/CollapseBlockPrivate/styles.module.css +27 -0
- package/dist/cjs/helperComponents/DesktopDroplist/DesktopDroplist.d.ts +7 -0
- package/dist/cjs/helperComponents/DesktopDroplist/DesktopDroplist.js +142 -0
- package/dist/cjs/helperComponents/DesktopDroplist/index.d.ts +1 -0
- package/dist/cjs/helperComponents/DesktopDroplist/index.js +17 -0
- package/dist/cjs/helperComponents/HiddenTabButton/HiddenTabButton.d.ts +7 -0
- package/dist/cjs/helperComponents/HiddenTabButton/HiddenTabButton.js +20 -0
- package/dist/cjs/helperComponents/HiddenTabButton/index.d.ts +1 -0
- package/dist/cjs/helperComponents/HiddenTabButton/index.js +17 -0
- package/dist/cjs/helperComponents/HiddenTabButton/styles.module.css +9 -0
- package/dist/cjs/helperComponents/ItemContent/ItemContent.d.ts +17 -0
- package/dist/cjs/helperComponents/ItemContent/ItemContent.js +27 -0
- package/dist/cjs/helperComponents/ItemContent/index.d.ts +1 -0
- package/dist/cjs/helperComponents/ItemContent/index.js +17 -0
- package/dist/cjs/helperComponents/ItemContent/styles.module.css +139 -0
- package/dist/cjs/helperComponents/ListEmptyState/ListEmptyState.d.ts +16 -0
- package/dist/cjs/helperComponents/ListEmptyState/ListEmptyState.js +25 -0
- package/dist/cjs/helperComponents/ListEmptyState/hooks.d.ts +10 -0
- package/dist/cjs/helperComponents/ListEmptyState/hooks.js +30 -0
- package/dist/cjs/helperComponents/ListEmptyState/index.d.ts +2 -0
- package/dist/cjs/helperComponents/ListEmptyState/index.js +20 -0
- package/dist/cjs/helperComponents/ListEmptyState/styles.module.css +8 -0
- package/dist/cjs/helperComponents/MobileDroplist/MobileDroplist.d.ts +9 -0
- package/dist/cjs/helperComponents/MobileDroplist/MobileDroplist.js +143 -0
- package/dist/cjs/helperComponents/MobileDroplist/MobileDroplist.module.css +16 -0
- package/dist/cjs/helperComponents/MobileDroplist/index.d.ts +1 -0
- package/dist/cjs/helperComponents/MobileDroplist/index.js +17 -0
- package/dist/cjs/helperComponents/MobileDroplist/utils.d.ts +11 -0
- package/dist/cjs/helperComponents/MobileDroplist/utils.js +65 -0
- package/dist/cjs/helperComponents/Separator/Separator.d.ts +20 -0
- package/dist/cjs/helperComponents/Separator/Separator.js +40 -0
- package/dist/cjs/helperComponents/Separator/constants.d.ts +3 -0
- package/dist/cjs/helperComponents/Separator/constants.js +10 -0
- package/dist/cjs/helperComponents/Separator/index.d.ts +1 -0
- package/dist/cjs/helperComponents/Separator/index.js +17 -0
- package/dist/cjs/helperComponents/Separator/styles.module.css +168 -0
- package/dist/cjs/helperComponents/index.d.ts +5 -0
- package/dist/cjs/helperComponents/index.js +21 -0
- package/dist/cjs/index.d.ts +9 -0
- package/dist/cjs/index.js +37 -0
- package/dist/cjs/locale/index.d.ts +79 -0
- package/dist/cjs/locale/index.js +44 -0
- package/dist/cjs/package.json +3 -0
- package/dist/cjs/style.css +1113 -0
- package/dist/cjs/types.d.ts +38 -0
- package/dist/cjs/types.js +2 -0
- package/dist/cjs/utils.d.ts +18 -0
- package/dist/cjs/utils.js +26 -0
- package/dist/esm/components/Items/AccordionItem/AccordionItem.d.ts +4 -0
- package/dist/esm/components/Items/AccordionItem/AccordionItem.js +34 -0
- package/dist/esm/components/Items/AccordionItem/index.d.ts +1 -0
- package/dist/esm/components/Items/AccordionItem/index.js +1 -0
- package/dist/esm/components/Items/BaseItem/BaseItem.d.ts +24 -0
- package/dist/esm/components/Items/BaseItem/BaseItem.js +112 -0
- package/dist/esm/components/Items/BaseItem/constants.d.ts +3 -0
- package/dist/esm/components/Items/BaseItem/constants.js +5 -0
- package/dist/esm/components/Items/BaseItem/index.d.ts +1 -0
- package/dist/esm/components/Items/BaseItem/index.js +1 -0
- package/dist/esm/components/Items/BaseItem/styles.module.css +312 -0
- package/dist/esm/components/Items/GroupSelectItem/GroupSelectItem.d.ts +4 -0
- package/dist/esm/components/Items/GroupSelectItem/GroupSelectItem.js +19 -0
- package/dist/esm/components/Items/GroupSelectItem/index.d.ts +1 -0
- package/dist/esm/components/Items/GroupSelectItem/index.js +1 -0
- package/dist/esm/components/Items/NextListItem/NextListItem.d.ts +2 -0
- package/dist/esm/components/Items/NextListItem/NextListItem.js +68 -0
- package/dist/esm/components/Items/NextListItem/constants.d.ts +2 -0
- package/dist/esm/components/Items/NextListItem/constants.js +10 -0
- package/dist/esm/components/Items/NextListItem/index.d.ts +1 -0
- package/dist/esm/components/Items/NextListItem/index.js +1 -0
- package/dist/esm/components/Items/NextListItem/types.d.ts +4 -0
- package/dist/esm/components/Items/NextListItem/types.js +1 -0
- package/dist/esm/components/Items/PinGroupItem/PinGroupItem.d.ts +7 -0
- package/dist/esm/components/Items/PinGroupItem/PinGroupItem.js +20 -0
- package/dist/esm/components/Items/PinGroupItem/index.d.ts +1 -0
- package/dist/esm/components/Items/PinGroupItem/index.js +1 -0
- package/dist/esm/components/Items/PinGroupItem/styles.module.css +12 -0
- package/dist/esm/components/Items/SearchItem/SearchItem.d.ts +17 -0
- package/dist/esm/components/Items/SearchItem/SearchItem.js +22 -0
- package/dist/esm/components/Items/SearchItem/index.d.ts +1 -0
- package/dist/esm/components/Items/SearchItem/index.js +1 -0
- package/dist/esm/components/Items/SearchItem/styles.module.css +13 -0
- package/dist/esm/components/Items/SimpleItem/components/DragHandle/DragHandle.d.ts +7 -0
- package/dist/esm/components/Items/SimpleItem/components/DragHandle/DragHandle.js +14 -0
- package/dist/esm/components/Items/SimpleItem/components/DragHandle/index.d.ts +1 -0
- package/dist/esm/components/Items/SimpleItem/components/DragHandle/index.js +1 -0
- package/dist/esm/components/Items/SimpleItem/components/OverlayDragHandle/OverlayDragHandle.d.ts +2 -0
- package/dist/esm/components/Items/SimpleItem/components/OverlayDragHandle/OverlayDragHandle.js +6 -0
- package/dist/esm/components/Items/SimpleItem/components/OverlayDragHandle/index.d.ts +1 -0
- package/dist/esm/components/Items/SimpleItem/components/OverlayDragHandle/index.js +1 -0
- package/dist/esm/components/Items/SimpleItem/components/SimpleGroupBlock/SimpleGroupBlock.d.ts +19 -0
- package/dist/esm/components/Items/SimpleItem/components/SimpleGroupBlock/SimpleGroupBlock.js +46 -0
- package/dist/esm/components/Items/SimpleItem/components/SimpleGroupBlock/index.d.ts +1 -0
- package/dist/esm/components/Items/SimpleItem/components/SimpleGroupBlock/index.js +1 -0
- package/dist/esm/components/Items/SimpleItem/components/SimpleGroupBlockOverlay/SimpleGroupBlockOverlay.d.ts +15 -0
- package/dist/esm/components/Items/SimpleItem/components/SimpleGroupBlockOverlay/SimpleGroupBlockOverlay.js +13 -0
- package/dist/esm/components/Items/SimpleItem/components/SimpleGroupBlockOverlay/index.d.ts +1 -0
- package/dist/esm/components/Items/SimpleItem/components/SimpleGroupBlockOverlay/index.js +1 -0
- package/dist/esm/components/Items/SimpleItem/components/SimpleItem/SimpleItem.d.ts +11 -0
- package/dist/esm/components/Items/SimpleItem/components/SimpleItem/SimpleItem.js +42 -0
- package/dist/esm/components/Items/SimpleItem/components/SimpleItem/index.d.ts +1 -0
- package/dist/esm/components/Items/SimpleItem/components/SimpleItem/index.js +1 -0
- package/dist/esm/components/Items/SimpleItem/components/SimpleItemOverlay/SimpleItemOverlay.d.ts +15 -0
- package/dist/esm/components/Items/SimpleItem/components/SimpleItemOverlay/SimpleItemOverlay.js +13 -0
- package/dist/esm/components/Items/SimpleItem/components/SimpleItemOverlay/index.d.ts +1 -0
- package/dist/esm/components/Items/SimpleItem/components/SimpleItemOverlay/index.js +1 -0
- package/dist/esm/components/Items/SimpleItem/components/index.d.ts +4 -0
- package/dist/esm/components/Items/SimpleItem/components/index.js +4 -0
- package/dist/esm/components/Items/SimpleItem/constants.d.ts +7 -0
- package/dist/esm/components/Items/SimpleItem/constants.js +6 -0
- package/dist/esm/components/Items/SimpleItem/index.d.ts +2 -0
- package/dist/esm/components/Items/SimpleItem/index.js +2 -0
- package/dist/esm/components/Items/SimpleItem/styles.module.css +96 -0
- package/dist/esm/components/Items/SimpleItem/types.d.ts +3 -0
- package/dist/esm/components/Items/SimpleItem/types.js +1 -0
- package/dist/esm/components/Items/SimpleItem/utils.d.ts +6 -0
- package/dist/esm/components/Items/SimpleItem/utils.js +8 -0
- package/dist/esm/components/Items/hooks.d.ts +23 -0
- package/dist/esm/components/Items/hooks.js +125 -0
- package/dist/esm/components/Items/index.d.ts +5 -0
- package/dist/esm/components/Items/index.js +5 -0
- package/dist/esm/components/Items/styles.module.css +168 -0
- package/dist/esm/components/Items/types.d.ts +168 -0
- package/dist/esm/components/Items/types.js +1 -0
- package/dist/esm/components/Items/utils.d.ts +52 -0
- package/dist/esm/components/Items/utils.js +203 -0
- package/dist/esm/components/Lists/Droplist/DropList.d.ts +21 -0
- package/dist/esm/components/Lists/Droplist/DropList.js +32 -0
- package/dist/esm/components/Lists/Droplist/constants.d.ts +8 -0
- package/dist/esm/components/Lists/Droplist/constants.js +7 -0
- package/dist/esm/components/Lists/Droplist/index.d.ts +1 -0
- package/dist/esm/components/Lists/Droplist/index.js +1 -0
- package/dist/esm/components/Lists/List/List.d.ts +61 -0
- package/dist/esm/components/Lists/List/List.js +56 -0
- package/dist/esm/components/Lists/List/index.d.ts +1 -0
- package/dist/esm/components/Lists/List/index.js +1 -0
- package/dist/esm/components/Lists/ListPrivate/ListPrivate.d.ts +18 -0
- package/dist/esm/components/Lists/ListPrivate/ListPrivate.js +295 -0
- package/dist/esm/components/Lists/ListPrivate/constants.d.ts +7 -0
- package/dist/esm/components/Lists/ListPrivate/constants.js +16 -0
- package/dist/esm/components/Lists/ListPrivate/index.d.ts +1 -0
- package/dist/esm/components/Lists/ListPrivate/index.js +1 -0
- package/dist/esm/components/Lists/ListPrivate/styles.module.css +80 -0
- package/dist/esm/components/Lists/contexts/CollapseProvider.d.ts +17 -0
- package/dist/esm/components/Lists/contexts/CollapseProvider.js +5 -0
- package/dist/esm/components/Lists/contexts/FocusListProvider.d.ts +9 -0
- package/dist/esm/components/Lists/contexts/FocusListProvider.js +5 -0
- package/dist/esm/components/Lists/contexts/NewListProvider.d.ts +33 -0
- package/dist/esm/components/Lists/contexts/NewListProvider.js +18 -0
- package/dist/esm/components/Lists/contexts/OpenListProvider.d.ts +6 -0
- package/dist/esm/components/Lists/contexts/OpenListProvider.js +6 -0
- package/dist/esm/components/Lists/contexts/SelectionProvider.d.ts +74 -0
- package/dist/esm/components/Lists/contexts/SelectionProvider.js +83 -0
- package/dist/esm/components/Lists/contexts/index.d.ts +5 -0
- package/dist/esm/components/Lists/contexts/index.js +5 -0
- package/dist/esm/components/Lists/hooks.d.ts +83 -0
- package/dist/esm/components/Lists/hooks.js +238 -0
- package/dist/esm/components/Lists/index.d.ts +5 -0
- package/dist/esm/components/Lists/index.js +5 -0
- package/dist/esm/components/Lists/styles.module.css +65 -0
- package/dist/esm/components/Lists/types.d.ts +200 -0
- package/dist/esm/components/Lists/types.js +1 -0
- package/dist/esm/components/Lists/utils.d.ts +8 -0
- package/dist/esm/components/Lists/utils.js +28 -0
- package/dist/esm/components/index.d.ts +2 -0
- package/dist/esm/components/index.js +2 -0
- package/dist/esm/constants.d.ts +48 -0
- package/dist/esm/constants.js +48 -0
- package/dist/esm/helperComponents/CollapseBlockPrivate/CollapseBlockPrivate.d.ts +9 -0
- package/dist/esm/helperComponents/CollapseBlockPrivate/CollapseBlockPrivate.js +9 -0
- package/dist/esm/helperComponents/CollapseBlockPrivate/index.d.ts +1 -0
- package/dist/esm/helperComponents/CollapseBlockPrivate/index.js +1 -0
- package/dist/esm/helperComponents/CollapseBlockPrivate/styles.module.css +27 -0
- package/dist/esm/helperComponents/DesktopDroplist/DesktopDroplist.d.ts +7 -0
- package/dist/esm/helperComponents/DesktopDroplist/DesktopDroplist.js +136 -0
- package/dist/esm/helperComponents/DesktopDroplist/index.d.ts +1 -0
- package/dist/esm/helperComponents/DesktopDroplist/index.js +1 -0
- package/dist/esm/helperComponents/HiddenTabButton/HiddenTabButton.d.ts +7 -0
- package/dist/esm/helperComponents/HiddenTabButton/HiddenTabButton.js +14 -0
- package/dist/esm/helperComponents/HiddenTabButton/index.d.ts +1 -0
- package/dist/esm/helperComponents/HiddenTabButton/index.js +1 -0
- package/dist/esm/helperComponents/HiddenTabButton/styles.module.css +9 -0
- package/dist/esm/helperComponents/ItemContent/ItemContent.d.ts +17 -0
- package/dist/esm/helperComponents/ItemContent/ItemContent.js +21 -0
- package/dist/esm/helperComponents/ItemContent/index.d.ts +1 -0
- package/dist/esm/helperComponents/ItemContent/index.js +1 -0
- package/dist/esm/helperComponents/ItemContent/styles.module.css +139 -0
- package/dist/esm/helperComponents/ListEmptyState/ListEmptyState.d.ts +16 -0
- package/dist/esm/helperComponents/ListEmptyState/ListEmptyState.js +19 -0
- package/dist/esm/helperComponents/ListEmptyState/hooks.d.ts +10 -0
- package/dist/esm/helperComponents/ListEmptyState/hooks.js +27 -0
- package/dist/esm/helperComponents/ListEmptyState/index.d.ts +2 -0
- package/dist/esm/helperComponents/ListEmptyState/index.js +2 -0
- package/dist/esm/helperComponents/ListEmptyState/styles.module.css +8 -0
- package/dist/esm/helperComponents/MobileDroplist/MobileDroplist.d.ts +9 -0
- package/dist/esm/helperComponents/MobileDroplist/MobileDroplist.js +137 -0
- package/dist/esm/helperComponents/MobileDroplist/MobileDroplist.module.css +16 -0
- package/dist/esm/helperComponents/MobileDroplist/index.d.ts +1 -0
- package/dist/esm/helperComponents/MobileDroplist/index.js +1 -0
- package/dist/esm/helperComponents/MobileDroplist/utils.d.ts +11 -0
- package/dist/esm/helperComponents/MobileDroplist/utils.js +61 -0
- package/dist/esm/helperComponents/Separator/Separator.d.ts +20 -0
- package/dist/esm/helperComponents/Separator/Separator.js +34 -0
- package/dist/esm/helperComponents/Separator/constants.d.ts +3 -0
- package/dist/esm/helperComponents/Separator/constants.js +7 -0
- package/dist/esm/helperComponents/Separator/index.d.ts +1 -0
- package/dist/esm/helperComponents/Separator/index.js +1 -0
- package/dist/esm/helperComponents/Separator/styles.module.css +168 -0
- package/dist/esm/helperComponents/index.d.ts +5 -0
- package/dist/esm/helperComponents/index.js +5 -0
- package/dist/esm/index.d.ts +9 -0
- package/dist/esm/index.js +14 -0
- package/dist/esm/locale/index.d.ts +79 -0
- package/dist/esm/locale/index.js +41 -0
- package/dist/esm/style.css +1113 -0
- package/dist/esm/types.d.ts +38 -0
- package/dist/esm/types.js +1 -0
- package/dist/esm/utils.d.ts +18 -0
- package/dist/esm/utils.js +19 -0
- package/dist/tsconfig.cjs.tsbuildinfo +1 -0
- package/dist/tsconfig.esm.tsbuildinfo +1 -0
- package/package.json +70 -0
- package/src/_iconSizes.scss +7 -0
- package/src/components/Items/AccordionItem/AccordionItem.tsx +79 -0
- package/src/components/Items/AccordionItem/index.ts +1 -0
- package/src/components/Items/BaseItem/BaseItem.tsx +274 -0
- package/src/components/Items/BaseItem/constants.ts +9 -0
- package/src/components/Items/BaseItem/index.ts +1 -0
- package/src/components/Items/BaseItem/styles.module.scss +330 -0
- package/src/components/Items/GroupSelectItem/GroupSelectItem.tsx +48 -0
- package/src/components/Items/GroupSelectItem/index.ts +1 -0
- package/src/components/Items/NextListItem/NextListItem.tsx +157 -0
- package/src/components/Items/NextListItem/constants.ts +12 -0
- package/src/components/Items/NextListItem/index.ts +1 -0
- package/src/components/Items/NextListItem/types.ts +3 -0
- package/src/components/Items/PinGroupItem/PinGroupItem.tsx +38 -0
- package/src/components/Items/PinGroupItem/index.ts +1 -0
- package/src/components/Items/PinGroupItem/styles.module.scss +14 -0
- package/src/components/Items/SearchItem/SearchItem.tsx +55 -0
- package/src/components/Items/SearchItem/index.ts +1 -0
- package/src/components/Items/SearchItem/styles.module.scss +26 -0
- package/src/components/Items/SimpleItem/components/DragHandle/DragHandle.tsx +27 -0
- package/src/components/Items/SimpleItem/components/DragHandle/index.ts +1 -0
- package/src/components/Items/SimpleItem/components/OverlayDragHandle/OverlayDragHandle.tsx +6 -0
- package/src/components/Items/SimpleItem/components/OverlayDragHandle/index.ts +1 -0
- package/src/components/Items/SimpleItem/components/SimpleGroupBlock/SimpleGroupBlock.tsx +87 -0
- package/src/components/Items/SimpleItem/components/SimpleGroupBlock/index.ts +1 -0
- package/src/components/Items/SimpleItem/components/SimpleGroupBlockOverlay/SimpleGroupBlockOverlay.tsx +33 -0
- package/src/components/Items/SimpleItem/components/SimpleGroupBlockOverlay/index.ts +1 -0
- package/src/components/Items/SimpleItem/components/SimpleItem/SimpleItem.tsx +70 -0
- package/src/components/Items/SimpleItem/components/SimpleItem/index.ts +1 -0
- package/src/components/Items/SimpleItem/components/SimpleItemOverlay/SimpleItemOverlay.tsx +26 -0
- package/src/components/Items/SimpleItem/components/SimpleItemOverlay/index.ts +1 -0
- package/src/components/Items/SimpleItem/components/index.ts +4 -0
- package/src/components/Items/SimpleItem/constants.ts +8 -0
- package/src/components/Items/SimpleItem/index.ts +2 -0
- package/src/components/Items/SimpleItem/styles.module.scss +149 -0
- package/src/components/Items/SimpleItem/types.ts +7 -0
- package/src/components/Items/SimpleItem/utils.ts +8 -0
- package/src/components/Items/hooks.tsx +233 -0
- package/src/components/Items/index.ts +5 -0
- package/src/components/Items/styles.module.scss +107 -0
- package/src/components/Items/types.ts +203 -0
- package/src/components/Items/utils.ts +302 -0
- package/src/components/Lists/Droplist/DropList.tsx +62 -0
- package/src/components/Lists/Droplist/constants.ts +11 -0
- package/src/components/Lists/Droplist/index.ts +1 -0
- package/src/components/Lists/List/List.tsx +155 -0
- package/src/components/Lists/List/index.ts +1 -0
- package/src/components/Lists/ListPrivate/ListPrivate.tsx +560 -0
- package/src/components/Lists/ListPrivate/constants.ts +22 -0
- package/src/components/Lists/ListPrivate/index.ts +1 -0
- package/src/components/Lists/ListPrivate/styles.module.scss +90 -0
- package/src/components/Lists/contexts/CollapseProvider.tsx +22 -0
- package/src/components/Lists/contexts/FocusListProvider.tsx +15 -0
- package/src/components/Lists/contexts/NewListProvider.tsx +76 -0
- package/src/components/Lists/contexts/OpenListProvider.tsx +13 -0
- package/src/components/Lists/contexts/SelectionProvider.tsx +214 -0
- package/src/components/Lists/contexts/index.ts +5 -0
- package/src/components/Lists/hooks.ts +351 -0
- package/src/components/Lists/index.ts +5 -0
- package/src/components/Lists/styles.module.scss +95 -0
- package/src/components/Lists/types.ts +231 -0
- package/src/components/Lists/utils.ts +32 -0
- package/src/components/index.ts +2 -0
- package/src/constants.ts +53 -0
- package/src/helperComponents/CollapseBlockPrivate/CollapseBlockPrivate.tsx +32 -0
- package/src/helperComponents/CollapseBlockPrivate/index.ts +1 -0
- package/src/helperComponents/CollapseBlockPrivate/styles.module.scss +32 -0
- package/src/helperComponents/DesktopDroplist/DesktopDroplist.tsx +313 -0
- package/src/helperComponents/DesktopDroplist/index.ts +1 -0
- package/src/helperComponents/HiddenTabButton/HiddenTabButton.tsx +35 -0
- package/src/helperComponents/HiddenTabButton/index.ts +1 -0
- package/src/helperComponents/HiddenTabButton/styles.module.scss +11 -0
- package/src/helperComponents/ItemContent/ItemContent.tsx +80 -0
- package/src/helperComponents/ItemContent/index.ts +1 -0
- package/src/helperComponents/ItemContent/styles.module.scss +100 -0
- package/src/helperComponents/ListEmptyState/ListEmptyState.tsx +52 -0
- package/src/helperComponents/ListEmptyState/hooks.ts +42 -0
- package/src/helperComponents/ListEmptyState/index.ts +2 -0
- package/src/helperComponents/ListEmptyState/styles.module.scss +11 -0
- package/src/helperComponents/MobileDroplist/MobileDroplist.module.scss +25 -0
- package/src/helperComponents/MobileDroplist/MobileDroplist.tsx +230 -0
- package/src/helperComponents/MobileDroplist/index.ts +1 -0
- package/src/helperComponents/MobileDroplist/utils.tsx +76 -0
- package/src/helperComponents/Separator/Separator.tsx +115 -0
- package/src/helperComponents/Separator/constants.ts +11 -0
- package/src/helperComponents/Separator/index.ts +1 -0
- package/src/helperComponents/Separator/styles.module.scss +255 -0
- package/src/helperComponents/index.ts +5 -0
- package/src/index.ts +19 -0
- package/src/locale/index.ts +45 -0
- package/src/types.ts +44 -0
- package/src/utils.ts +26 -0
package/README.md
ADDED
|
@@ -0,0 +1,1413 @@
|
|
|
1
|
+
# List
|
|
2
|
+
|
|
3
|
+
`@cloud-ru/ds-list` — Списочный UI — компонент List для плоских/вложенных списков с выбором, группами и поиском и Droplist — тот же список в поповере.
|
|
4
|
+
|
|
5
|
+
Пакет `@cloud-ru/ds-list` собирает списочный UI дизайн-системы: плоские и вложенные списки, списки с выбором (single / multiple), группы с раскрытием, поиск, закреплённые элементы, виртуализацию и Droplist — тот же список в popover.
|
|
6
|
+
|
|
7
|
+
## Когда использовать
|
|
8
|
+
|
|
9
|
+
| Задача | Как решить |
|
|
10
|
+
|--------|------------|
|
|
11
|
+
| Навигация/меню/settings sidebar | `List` с `items` |
|
|
12
|
+
| Выбор из коллекции (радио-группа/чекбоксы) | `List` + `selection={{ mode: 'single' \| 'multiple', ... }}` |
|
|
13
|
+
| Выпадашка-селектор у кнопки/поля | `Droplist` с `children`-триггером |
|
|
14
|
+
| Группы с раскрытием (inbox/starred/folders) | `items` типа `{ type: 'collapse', items: [...] }` + `collapse` |
|
|
15
|
+
| Длинный список (1k+) | `virtualized` на `List` |
|
|
16
|
+
| Закреплённые действия сверху/снизу | `pinTop` / `pinBottom` |
|
|
17
|
+
|
|
18
|
+
Когда **не** нужен `List`:
|
|
19
|
+
|
|
20
|
+
- Простой набор из 2–4 кнопок — используйте **`Button`** + layout.
|
|
21
|
+
- Табличные данные с сортировкой/фильтрацией — используйте `Table`.
|
|
22
|
+
- Многошаговая форма — используйте `Stepper`.
|
|
23
|
+
|
|
24
|
+
## Установка
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
pnpm add @cloud-ru/ds-list
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
```ts
|
|
31
|
+
import { List, Droplist, ItemContent } from '@cloud-ru/ds-list'
|
|
32
|
+
import '@cloud-ru/ds-list/style.css'
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Figma
|
|
36
|
+
|
|
37
|
+
Все три компонента живут в одном Figma-файле «Состояния для list / tab / toggles». Ссылки на конкретные узлы — на страницах компонентов.
|
|
38
|
+
## Состав пакета
|
|
39
|
+
|
|
40
|
+
- ****List**** — основной компонент. Принимает `items` (+ `pinTop` / `pinBottom` / `footer`), управляет выбором через `selection`, раскрытием групп через `collapse`, поиском через `search`. Поддерживает виртуализацию для 1000+ элементов.
|
|
41
|
+
- ****Droplist**** — тот же список в popover. Оборачивает `children`-триггер и открывает список рядом с ним. Передаёт почти все пропсы `List`.
|
|
42
|
+
- ****ReorderableList**** — список с drag&drop-переупорядочиванием строк через `@dnd-kit` (плюс `ReorderableDroplist` — то же в поповере).
|
|
43
|
+
- ****ItemContent**** — каноничная разметка содержимого item: `label` (заголовок), `caption` (мета справа), `description` (подпись снизу). Используется как значение `item.content`.
|
|
44
|
+
|
|
45
|
+
## Общие принципы
|
|
46
|
+
|
|
47
|
+
- **Contract первый, стайлинг второй.** Элементы описываются как данные (`items: Item[]`), а не как JSX. Это даёт стабильную клавиатурную навигацию, selection и поиск «из коробки».
|
|
48
|
+
- **ItemContent — единый слот контента.** Разметку внутри элемента задаёт не потребитель, а `ItemContent` — чтобы заголовок / caption / description выравнивались одинаково во всех пакетах.
|
|
49
|
+
- **Controlled/uncontrolled симметричны.** У `selection`, `collapse` и `search` одинаковая форма: `defaultValue` / `value` + `onChange`. Выбирайте по тому, где должен жить state.
|
|
50
|
+
- **Виртуализация — осознанный выбор.** Включайте `virtualized` только при 1k+ элементов. На коротких списках виртуализация ломает layout-assumptions (динамическая высота, focus-into-view).
|
|
51
|
+
|
|
52
|
+
## List
|
|
53
|
+
|
|
54
|
+
Основной компонент списка. Управляет items, выбором, раскрытием групп, поиском, виртуализацией и закреплёнными элементами.
|
|
55
|
+
|
|
56
|
+
Плоский или вложенный список элементов с клавиатурной навигацией, выбором, раскрытием групп и поиском. Элементы задаются декларативно через `items`, `pinTop`, `pinBottom` — компонент сам строит DOM, управляет фокусом и эмитит изменения выбора.
|
|
57
|
+
|
|
58
|
+
### Когда использовать
|
|
59
|
+
|
|
60
|
+
- Навигация (sidebar, меню аккаунта, breadcrumbs popover).
|
|
61
|
+
- Селектор-значение (часовой пояс, валюта, язык) — чаще всего вместе с **Droplist**.
|
|
62
|
+
- Список объектов с действиями: письма, файлы, задачи.
|
|
63
|
+
- Иерархия с раскрытием (folders, workspace → projects → tasks).
|
|
64
|
+
- Длинные списки (1k+) через `virtualized`.
|
|
65
|
+
|
|
66
|
+
Когда **не** нужен:
|
|
67
|
+
|
|
68
|
+
- Таблицы с колонками — `Table`.
|
|
69
|
+
- Короткий набор действий (≤ 4) — кнопки.
|
|
70
|
+
|
|
71
|
+
### Анатомия
|
|
72
|
+
|
|
73
|
+
#### Size (default `s`)
|
|
74
|
+
|
|
75
|
+
- `s` — компактный, дефолт. Droplist'ы, плотные sidebar'ы, настройки.
|
|
76
|
+
- `m` — средний. Основные списки объектов (письма, задачи).
|
|
77
|
+
- `l` — крупный. Mobile, списки с описаниями, главные навигационные меню.
|
|
78
|
+
|
|
79
|
+
#### Selection (default off)
|
|
80
|
+
|
|
81
|
+
- Без `selection` — клик = `onClick` элемента, состояния «выбран» нет. Подходит для навигации.
|
|
82
|
+
- `selection={{ mode: 'single' }}` — один выбранный элемент, `value: ItemId`. Селектор значения (валюта, регион).
|
|
83
|
+
- `selection={{ mode: 'multiple' }}` — множественный выбор, `value: ItemId[]`. Фильтры, настройки, права доступа.
|
|
84
|
+
|
|
85
|
+
Visual signal: marker (вертикальная полоса) + фоновая заливка. `marker` можно отключить (`marker={false}`), если родительский layout уже подсвечивает активный элемент.
|
|
86
|
+
|
|
87
|
+
#### Switch-презентация выбора
|
|
88
|
+
|
|
89
|
+
У `BaseItem` есть проп `switch`. При `switch: true` элемент рисует на правом краю `Switch` вместо чекбокса/маркера, а `isChecked` управляет положением тумблера. Применяется в списках настроек, где «выбор» — это включение функции (уведомления, доступы, интеграции), а не пометка элемента.
|
|
90
|
+
|
|
91
|
+
- `switch` работает вместе с `selection` (`mode: 'multiple'` или `'single'`) — состояние тумблера берётся из выбора.
|
|
92
|
+
- При `switch: true` маркер и заливка не рисуются: единственный носитель состояния — сам тумблер.
|
|
93
|
+
- `showSwitchIcon` — флаг иконки внутри тумблера (на момент Phase 5 не передаётся в `@cloud-ru/ds-toggles`, зарезервирован).
|
|
94
|
+
|
|
95
|
+
#### Group variant (default `subtitleTertiary`)
|
|
96
|
+
|
|
97
|
+
Тип `group` / `group-select` принимает `groupVariant` — оформление заголовка группы:
|
|
98
|
+
|
|
99
|
+
- `subtitle` — заголовок-подзаголовок: контрастный текст, акцент на названии секции.
|
|
100
|
+
- `subtitleTertiary` — приглушённый третичный текст, для второстепенных группировок.
|
|
101
|
+
|
|
102
|
+
Дополнительно `divider: true` рисует разделитель над группой.
|
|
103
|
+
|
|
104
|
+
#### Item shape
|
|
105
|
+
|
|
106
|
+
Один элемент = одна строка данных. Слоты:
|
|
107
|
+
|
|
108
|
+
- `label` — заголовок (обязателен).
|
|
109
|
+
- `caption` — мета справа (счётчик, дата, badge).
|
|
110
|
+
- `description` — подпись под заголовком (1–2 строки).
|
|
111
|
+
- `beforeContent` — иконка / аватар слева.
|
|
112
|
+
- `afterContent` — слот справа. Иконка рендерится в квадрате размера иконки; произвольный контент (счётчик, badge, tag) — в натуральном размере, без обрезки до иконочного квадрата.
|
|
113
|
+
|
|
114
|
+
Truncate управляется через `truncate` на `ItemContent` — см. страницу **ItemContent**.
|
|
115
|
+
|
|
116
|
+
#### States
|
|
117
|
+
|
|
118
|
+
- `loading` — показывает skeleton вместо items.
|
|
119
|
+
- `dataFiltered` — флаг, что текущий пустой массив — результат фильтра/поиска. Компонент покажет `noResultsState`.
|
|
120
|
+
- `dataError` — ошибка загрузки, рендерится `errorDataState`.
|
|
121
|
+
- `noDataState` / `noResultsState` / `errorDataState` — конфигурация empty-state (`description`, `icon`, `footer`).
|
|
122
|
+
|
|
123
|
+
### Примеры использования
|
|
124
|
+
|
|
125
|
+
#### Базовый плоский список
|
|
126
|
+
|
|
127
|
+
items — массив объектов с id и content.
|
|
128
|
+
|
|
129
|
+
```tsx
|
|
130
|
+
import { List } from '@cloud-ru/ds-list';
|
|
131
|
+
|
|
132
|
+
import styles from './styles.module.scss';
|
|
133
|
+
|
|
134
|
+
export function BasicList() {
|
|
135
|
+
return (
|
|
136
|
+
<div className={styles.box}>
|
|
137
|
+
<List
|
|
138
|
+
size='s'
|
|
139
|
+
items={[
|
|
140
|
+
{ id: 'inbox', content: { label: 'Входящие', caption: '12' } },
|
|
141
|
+
{ id: 'sent', content: { label: 'Отправленные' } },
|
|
142
|
+
{ id: 'archive', content: { label: 'Архив', caption: '238' } },
|
|
143
|
+
{ id: 'trash', content: { label: 'Корзина', description: 'Удаляется через 30 дней' } },
|
|
144
|
+
]}
|
|
145
|
+
/>
|
|
146
|
+
</div>
|
|
147
|
+
);
|
|
148
|
+
}
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
#### Single selection
|
|
152
|
+
|
|
153
|
+
selection={{ mode: 'single', value, onChange }} — контролируемый выбор одного элемента.
|
|
154
|
+
|
|
155
|
+
```tsx
|
|
156
|
+
import { List } from '@cloud-ru/ds-list';
|
|
157
|
+
import { useState } from 'react';
|
|
158
|
+
|
|
159
|
+
import styles from './styles.module.scss';
|
|
160
|
+
|
|
161
|
+
export function SingleSelection() {
|
|
162
|
+
const [value, setValue] = useState<string | number | undefined>('usd');
|
|
163
|
+
|
|
164
|
+
return (
|
|
165
|
+
<div className={styles.box}>
|
|
166
|
+
<List
|
|
167
|
+
size='s'
|
|
168
|
+
marker
|
|
169
|
+
selection={{ mode: 'single', value, onChange: setValue }}
|
|
170
|
+
items={[
|
|
171
|
+
{ id: 'usd', content: { label: 'Доллар США', caption: 'USD' } },
|
|
172
|
+
{ id: 'eur', content: { label: 'Евро', caption: 'EUR' } },
|
|
173
|
+
{ id: 'rub', content: { label: 'Российский рубль', caption: 'RUB' } },
|
|
174
|
+
{ id: 'cny', content: { label: 'Китайский юань', caption: 'CNY' } },
|
|
175
|
+
]}
|
|
176
|
+
/>
|
|
177
|
+
</div>
|
|
178
|
+
);
|
|
179
|
+
}
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
#### Multiple selection
|
|
183
|
+
|
|
184
|
+
В режиме multiple value — массив ItemId.
|
|
185
|
+
|
|
186
|
+
```tsx
|
|
187
|
+
import { List } from '@cloud-ru/ds-list';
|
|
188
|
+
import { useState } from 'react';
|
|
189
|
+
|
|
190
|
+
import styles from './styles.module.scss';
|
|
191
|
+
|
|
192
|
+
export function MultipleSelection() {
|
|
193
|
+
const [value, setValue] = useState<(string | number)[]>(['email', 'push']);
|
|
194
|
+
|
|
195
|
+
return (
|
|
196
|
+
<div className={styles.box}>
|
|
197
|
+
<List
|
|
198
|
+
size='s'
|
|
199
|
+
selection={{ mode: 'multiple', value, onChange: setValue }}
|
|
200
|
+
items={[
|
|
201
|
+
{ id: 'email', content: { label: 'Email' } },
|
|
202
|
+
{ id: 'push', content: { label: 'Push-уведомления' } },
|
|
203
|
+
{ id: 'sms', content: { label: 'SMS' } },
|
|
204
|
+
{ id: 'telegram', content: { label: 'Telegram', description: 'Требует привязки аккаунта' } },
|
|
205
|
+
]}
|
|
206
|
+
/>
|
|
207
|
+
</div>
|
|
208
|
+
);
|
|
209
|
+
}
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
#### Группы с раскрытием
|
|
213
|
+
|
|
214
|
+
type: 'collapse' + collapse.defaultValue. Подходит для folders / workspace / sidebar-групп.
|
|
215
|
+
|
|
216
|
+
```tsx
|
|
217
|
+
import { List } from '@cloud-ru/ds-list';
|
|
218
|
+
|
|
219
|
+
import styles from './styles.module.scss';
|
|
220
|
+
|
|
221
|
+
export function GroupsCollapsible() {
|
|
222
|
+
return (
|
|
223
|
+
<div className={styles.box}>
|
|
224
|
+
<List
|
|
225
|
+
size='s'
|
|
226
|
+
collapse={{ defaultValue: ['projects'] }}
|
|
227
|
+
items={[
|
|
228
|
+
{
|
|
229
|
+
type: 'collapse',
|
|
230
|
+
id: 'projects',
|
|
231
|
+
content: { label: 'Проекты' },
|
|
232
|
+
items: [
|
|
233
|
+
{ id: 'p-web', content: { label: 'Web-портал' } },
|
|
234
|
+
{ id: 'p-mobile', content: { label: 'Mobile' } },
|
|
235
|
+
{ id: 'p-admin', content: { label: 'Admin' } },
|
|
236
|
+
],
|
|
237
|
+
},
|
|
238
|
+
{
|
|
239
|
+
type: 'collapse',
|
|
240
|
+
id: 'settings',
|
|
241
|
+
content: { label: 'Настройки' },
|
|
242
|
+
items: [
|
|
243
|
+
{ id: 's-profile', content: { label: 'Профиль' } },
|
|
244
|
+
{ id: 's-team', content: { label: 'Команда' } },
|
|
245
|
+
{ id: 's-billing', content: { label: 'Оплата' } },
|
|
246
|
+
],
|
|
247
|
+
},
|
|
248
|
+
]}
|
|
249
|
+
/>
|
|
250
|
+
</div>
|
|
251
|
+
);
|
|
252
|
+
}
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
#### Три уровня вложенности
|
|
256
|
+
|
|
257
|
+
Группы внутри групп — подходит для каталогов, файловых деревьев.
|
|
258
|
+
|
|
259
|
+
```tsx
|
|
260
|
+
import { List } from '@cloud-ru/ds-list';
|
|
261
|
+
|
|
262
|
+
import styles from './styles.module.scss';
|
|
263
|
+
|
|
264
|
+
export function NestedLevels() {
|
|
265
|
+
return (
|
|
266
|
+
<div className={styles.box}>
|
|
267
|
+
<List
|
|
268
|
+
size='s'
|
|
269
|
+
collapse={{ defaultValue: ['catalog', 'catalog/books'] }}
|
|
270
|
+
items={[
|
|
271
|
+
{
|
|
272
|
+
type: 'collapse',
|
|
273
|
+
id: 'catalog',
|
|
274
|
+
content: { label: 'Каталог' },
|
|
275
|
+
items: [
|
|
276
|
+
{
|
|
277
|
+
type: 'collapse',
|
|
278
|
+
id: 'catalog/books',
|
|
279
|
+
content: { label: 'Книги' },
|
|
280
|
+
items: [
|
|
281
|
+
{ id: 'catalog/books/fiction', content: { label: 'Художественные' } },
|
|
282
|
+
{ id: 'catalog/books/science', content: { label: 'Научные' } },
|
|
283
|
+
],
|
|
284
|
+
},
|
|
285
|
+
{
|
|
286
|
+
type: 'collapse',
|
|
287
|
+
id: 'catalog/music',
|
|
288
|
+
content: { label: 'Музыка' },
|
|
289
|
+
items: [
|
|
290
|
+
{ id: 'catalog/music/rock', content: { label: 'Рок' } },
|
|
291
|
+
{ id: 'catalog/music/jazz', content: { label: 'Джаз' } },
|
|
292
|
+
],
|
|
293
|
+
},
|
|
294
|
+
],
|
|
295
|
+
},
|
|
296
|
+
]}
|
|
297
|
+
/>
|
|
298
|
+
</div>
|
|
299
|
+
);
|
|
300
|
+
}
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
#### Поиск внутри списка
|
|
304
|
+
|
|
305
|
+
search={{ value, onChange }} — встроенная строка поиска. Фильтрация items — на стороне потребителя.
|
|
306
|
+
|
|
307
|
+
```tsx
|
|
308
|
+
import { List } from '@cloud-ru/ds-list';
|
|
309
|
+
import { useMemo, useState } from 'react';
|
|
310
|
+
|
|
311
|
+
import styles from './styles.module.scss';
|
|
312
|
+
|
|
313
|
+
const ALL = [
|
|
314
|
+
'Антон',
|
|
315
|
+
'Борис',
|
|
316
|
+
'Виктор',
|
|
317
|
+
'Галина',
|
|
318
|
+
'Дарья',
|
|
319
|
+
'Евгений',
|
|
320
|
+
'Жанна',
|
|
321
|
+
'Зоя',
|
|
322
|
+
'Игорь',
|
|
323
|
+
'Ксения',
|
|
324
|
+
'Лев',
|
|
325
|
+
'Мария',
|
|
326
|
+
];
|
|
327
|
+
|
|
328
|
+
export function WithSearch() {
|
|
329
|
+
const [query, setQuery] = useState('');
|
|
330
|
+
|
|
331
|
+
const items = useMemo(
|
|
332
|
+
() =>
|
|
333
|
+
ALL.filter(name => name.toLowerCase().includes(query.toLowerCase())).map(name => ({
|
|
334
|
+
id: name,
|
|
335
|
+
content: { label: name },
|
|
336
|
+
})),
|
|
337
|
+
[query],
|
|
338
|
+
);
|
|
339
|
+
|
|
340
|
+
return (
|
|
341
|
+
<div className={styles.box}>
|
|
342
|
+
<List size='s' search={{ value: query, onChange: setQuery, placeholder: 'Поиск по имени' }} items={items} />
|
|
343
|
+
</div>
|
|
344
|
+
);
|
|
345
|
+
}
|
|
346
|
+
```
|
|
347
|
+
|
|
348
|
+
#### Закреплённые элементы
|
|
349
|
+
|
|
350
|
+
pinTop / pinBottom — списки, которые не участвуют в scroll основного массива.
|
|
351
|
+
|
|
352
|
+
```tsx
|
|
353
|
+
import { List } from '@cloud-ru/ds-list';
|
|
354
|
+
|
|
355
|
+
import styles from './styles.module.scss';
|
|
356
|
+
|
|
357
|
+
export function PinnedItems() {
|
|
358
|
+
return (
|
|
359
|
+
<div className={styles.box}>
|
|
360
|
+
<List
|
|
361
|
+
size='s'
|
|
362
|
+
pinTop={[
|
|
363
|
+
{ id: 'all', content: { label: 'Все задачи', caption: '128' } },
|
|
364
|
+
{ id: 'mine', content: { label: 'Мои задачи', caption: '14' } },
|
|
365
|
+
]}
|
|
366
|
+
items={[
|
|
367
|
+
{ id: 'design', content: { label: 'Дизайн' } },
|
|
368
|
+
{ id: 'backend', content: { label: 'Бэкенд' } },
|
|
369
|
+
{ id: 'frontend', content: { label: 'Фронтенд' } },
|
|
370
|
+
{ id: 'qa', content: { label: 'QA' } },
|
|
371
|
+
]}
|
|
372
|
+
pinBottom={[{ id: 'archive', content: { label: 'Архив', description: 'Завершённые задачи' } }]}
|
|
373
|
+
/>
|
|
374
|
+
</div>
|
|
375
|
+
);
|
|
376
|
+
}
|
|
377
|
+
```
|
|
378
|
+
|
|
379
|
+
#### Виртуализация
|
|
380
|
+
|
|
381
|
+
virtualized — рендерит только видимые строки. Включайте при 1000+ элементов.
|
|
382
|
+
|
|
383
|
+
```tsx
|
|
384
|
+
import { Button } from '@cloud-ru/ds-button';
|
|
385
|
+
import { List } from '@cloud-ru/ds-list';
|
|
386
|
+
import { useMemo, useState } from 'react';
|
|
387
|
+
|
|
388
|
+
import styles from './styles.module.scss';
|
|
389
|
+
|
|
390
|
+
const TOTAL = 10_000;
|
|
391
|
+
|
|
392
|
+
export function Virtualized() {
|
|
393
|
+
const items = useMemo(
|
|
394
|
+
() =>
|
|
395
|
+
Array.from({ length: TOTAL }, (_, i) => ({
|
|
396
|
+
id: `row-${i}`,
|
|
397
|
+
content: { label: `Запись #${i + 1}`, caption: i % 5 === 0 ? 'new' : undefined },
|
|
398
|
+
})),
|
|
399
|
+
[],
|
|
400
|
+
);
|
|
401
|
+
|
|
402
|
+
const [value, setValue] = useState<string | number | undefined>('row-0');
|
|
403
|
+
|
|
404
|
+
return (
|
|
405
|
+
<div className={styles.wrapper}>
|
|
406
|
+
<Button
|
|
407
|
+
size='s'
|
|
408
|
+
appearance='neutral'
|
|
409
|
+
view='outline'
|
|
410
|
+
label='Прыгнуть к случайной записи'
|
|
411
|
+
onClick={() => setValue(`row-${Math.floor(Math.random() * TOTAL)}`)}
|
|
412
|
+
/>
|
|
413
|
+
<List
|
|
414
|
+
size='s'
|
|
415
|
+
virtualized
|
|
416
|
+
scroll
|
|
417
|
+
limitedScrollHeight
|
|
418
|
+
scrollToSelectedItem
|
|
419
|
+
selection={{ mode: 'single', value, onChange: setValue }}
|
|
420
|
+
items={items}
|
|
421
|
+
/>
|
|
422
|
+
</div>
|
|
423
|
+
);
|
|
424
|
+
}
|
|
425
|
+
```
|
|
426
|
+
|
|
427
|
+
#### Empty state
|
|
428
|
+
|
|
429
|
+
dataFiltered + noResultsState — контролируемое сообщение при пустом результате поиска.
|
|
430
|
+
|
|
431
|
+
```tsx
|
|
432
|
+
import { List } from '@cloud-ru/ds-list';
|
|
433
|
+
import { useState } from 'react';
|
|
434
|
+
|
|
435
|
+
import styles from './styles.module.scss';
|
|
436
|
+
|
|
437
|
+
export function EmptyState() {
|
|
438
|
+
const [query, setQuery] = useState('qwerty');
|
|
439
|
+
|
|
440
|
+
const items = query.length > 3 ? [] : [{ id: 'a', content: { label: 'Пример результата' } }];
|
|
441
|
+
|
|
442
|
+
return (
|
|
443
|
+
<div className={styles.box}>
|
|
444
|
+
<List
|
|
445
|
+
size='s'
|
|
446
|
+
search={{ value: query, onChange: setQuery, placeholder: 'Поиск' }}
|
|
447
|
+
items={items}
|
|
448
|
+
dataFiltered
|
|
449
|
+
noResultsState={{
|
|
450
|
+
content: 'Ничего не нашли. Проверьте раскладку или измените запрос.',
|
|
451
|
+
}}
|
|
452
|
+
/>
|
|
453
|
+
</div>
|
|
454
|
+
);
|
|
455
|
+
}
|
|
456
|
+
```
|
|
457
|
+
|
|
458
|
+
#### Обёртка айтема и неактивный элемент
|
|
459
|
+
|
|
460
|
+
itemWrapRender оборачивает item в Tooltip; inactive выводит элемент из навигации и selection, оставляя его видимым.
|
|
461
|
+
|
|
462
|
+
```tsx
|
|
463
|
+
import { List } from '@cloud-ru/ds-list';
|
|
464
|
+
import { ReactNode } from 'react';
|
|
465
|
+
|
|
466
|
+
import styles from './styles.module.scss';
|
|
467
|
+
|
|
468
|
+
const asExternalLink = (href: string) =>
|
|
469
|
+
function (item: ReactNode) {
|
|
470
|
+
return (
|
|
471
|
+
<a href={href} target='_blank' rel='noreferrer' className={styles.link}>
|
|
472
|
+
{item}
|
|
473
|
+
</a>
|
|
474
|
+
);
|
|
475
|
+
};
|
|
476
|
+
|
|
477
|
+
export function ListItemWrap() {
|
|
478
|
+
return (
|
|
479
|
+
<div className={styles.box}>
|
|
480
|
+
<List
|
|
481
|
+
size='s'
|
|
482
|
+
items={[
|
|
483
|
+
{
|
|
484
|
+
id: 'prod',
|
|
485
|
+
content: { label: 'Production', caption: 'open' },
|
|
486
|
+
itemWrapRender: asExternalLink('https://console.cloud.ru/prod'),
|
|
487
|
+
},
|
|
488
|
+
{
|
|
489
|
+
id: 'staging',
|
|
490
|
+
content: { label: 'Staging', caption: 'open' },
|
|
491
|
+
itemWrapRender: asExternalLink('https://console.cloud.ru/staging'),
|
|
492
|
+
},
|
|
493
|
+
{
|
|
494
|
+
id: 'legacy',
|
|
495
|
+
content: { label: 'Legacy', description: 'Среда выведена из эксплуатации — недоступна для перехода' },
|
|
496
|
+
inactive: true,
|
|
497
|
+
},
|
|
498
|
+
]}
|
|
499
|
+
/>
|
|
500
|
+
</div>
|
|
501
|
+
);
|
|
502
|
+
}
|
|
503
|
+
```
|
|
504
|
+
|
|
505
|
+
### Props
|
|
506
|
+
|
|
507
|
+
| Prop | Type | Default | Description |
|
|
508
|
+
|------|------|---------|-------------|
|
|
509
|
+
| `barHideStrategy` | `"leave"` \| `"move"` \| `"never"` \| `"scroll"` | — | Управление скрытием скролл баров: <br/> <br> - `Never` - показывать всегда <br/> <br> - `Leave` - скрывать когда курсор покидает компонент <br/> <br> - `Scroll` - показывать только когда происходит скроллинг <br/> <br> - `Move` - показывать при движении курсора над компонентом |
|
|
510
|
+
| `className` | `string` | — | CSS-класс |
|
|
511
|
+
| `collapse` | `CollapseState` | `{}` | Настройки раскрытия элементов |
|
|
512
|
+
| `contentRender` | `((props: ContentRenderProps) => ReactNode)` | — | Рендер функция основного контента айтема |
|
|
513
|
+
| `data-test-id` | `string` | — | |
|
|
514
|
+
| `dataError` | `boolean` | — | Загрузка данных завершилась ошибкой: показывается `errorDataState` |
|
|
515
|
+
| `dataFiltered` | `boolean` | — | Текущий пустой список — результат поиска/фильтра: показывается `noResultsState` вместо `noDataState` |
|
|
516
|
+
| `errorDataState` | `EmptyStateProps` | — | Экран при ошибке запроса |
|
|
517
|
+
| `footer` | `ReactNode ;` | — | Кастомизируемый элемент в конце списка |
|
|
518
|
+
| `footerActiveElementsRefs` | `RefObject<HTMLElement>[]` | — | Список ссылок на кастомные элементы, помещенные в специальную секцию внизу списка |
|
|
519
|
+
| `footerDivider` | `boolean` | — | Показывать divider между body и footer (Figma `dropdownContainer.dividerWrapper` снизу) |
|
|
520
|
+
| `hasListInFocusChain` | `boolean` | `true` | Флаг, отвечающий за включение самого родительского контейнера листа в цепочку фокусирующихся элементов |
|
|
521
|
+
| `header` | `ReactNode ;` | — | Кастомизируемый элемент в начале списка — Figma `dropdownContainer.topBar`. <br/> Подходит для заголовка / справочного блока над поиском. |
|
|
522
|
+
| `headerDivider` | `boolean` | — | Показывать divider между header и body (Figma `dropdownContainer.dividerWrapper` сверху) |
|
|
523
|
+
| `items` | `Item[]` | `[]` | Основные элементы списка |
|
|
524
|
+
| `keyboardNavigationRef` | `RefObject<{ focusItem(id: ItemId): void; }>` | — | Ссылка на управление навигацией листа с клавиатуры |
|
|
525
|
+
| `limitedScrollHeight` | `boolean` | — | Ограничить максимальную высоту скролл-контейнера в зависимости от `size` |
|
|
526
|
+
| `loading` | `boolean` | — | Флаг, отвечающий за состояние загрузки списка |
|
|
527
|
+
| `marker` | `boolean` | `true` | Отображать ли маркер у выбранного элемента списка |
|
|
528
|
+
| `noDataState` | `EmptyStateProps` | — | Экран при отсутствии данных |
|
|
529
|
+
| `noResultsState` | `EmptyStateProps` | — | Экран при отсутствии результатов поиска или фильтров |
|
|
530
|
+
| `onKeyDown` | `((e: KeyboardEvent<HTMLElement>) => void)` | — | Обработчик события по нажатию клавиш |
|
|
531
|
+
| `onScroll` | `((event?: Event) => void)` | — | Колбек на скролл прокручиваемого списка |
|
|
532
|
+
| `pinBottom` | `Item[]` | `[]` | Элементы списка, закрепленные снизу |
|
|
533
|
+
| `pinTop` | `Item[]` | `[]` | Элементы списка, закрепленные сверху |
|
|
534
|
+
| `scroll` | `boolean` | — | Включить ли скролл для основной части списка |
|
|
535
|
+
| `scrollContainerClassName` | `string` | — | CSS-класс для scroll обертки основного списка айтемов |
|
|
536
|
+
| `scrollContainerRef` | `Ref<HTMLElement>` | — | Ссылка на контейнер, который скроллится |
|
|
537
|
+
| `scrollRef` | `Ref<HTMLElement>` | — | Ссылка на элемент, обозначающий самый конец прокручиваемого списка |
|
|
538
|
+
| `scrollToSelectedItem` | `boolean` | — | Флаг, отвечающий за прокручивание до выбранного элемента |
|
|
539
|
+
| `search` | `SearchState` | — | Настройки поисковой строки |
|
|
540
|
+
| `selection` | `SelectionMultipleState \| SelectionSingleState` | — | Настройки выбора элементов. `mode: 'single'` — один выбранный элемент (`value: ItemId`), <br/> `mode: 'multiple'` — множественный выбор (`value: ItemId[]`). Без `selection` выбора нет — <br/> клик вызывает только `onClick` элемента. |
|
|
541
|
+
| `size` | `"l"` \| `"m"` \| `"s"` | `m` | Размер списка |
|
|
542
|
+
| `tabIndex` | `number` | `0` | `tabIndex` корневого элемента списка (для управления порядком фокуса) |
|
|
543
|
+
| `untouchableScrollbars` | `boolean` | — | Отключает возможность взаимодействовать со скролбарами мышью. |
|
|
544
|
+
| `virtualized` | `boolean` | — | Включить виртуализацию элементов списка. Рекомендуется при количестве элементов от 1000. |
|
|
545
|
+
|
|
546
|
+
### Смотри также
|
|
547
|
+
|
|
548
|
+
- **Droplist** — этот список в popover.
|
|
549
|
+
- **ReorderableList** — тот же список, но с drag&drop-переупорядочиванием строк.
|
|
550
|
+
- **ItemContent** — каноничная разметка item.
|
|
551
|
+
### Item types
|
|
552
|
+
|
|
553
|
+
- `BaseItem` — обычный элемент с `content` / `beforeContent` / `afterContent`; опционально `switch: true` для тумблер-презентации выбора.
|
|
554
|
+
- `{ type: 'collapse', items }` — группа, раскрывается кликом по заголовку; управление — через `collapse`.
|
|
555
|
+
- `{ type: 'next-list', items, placement }` — раскрытие в соседний popover (для каскадных меню).
|
|
556
|
+
- `{ type: 'group', items, groupVariant }` / `{ type: 'group-select', items, groupVariant }` — визуальная группа с label, `groupVariant` (`subtitle` / `subtitleTertiary`), опциональным `divider` и «выбрать всё» (`group-select`).
|
|
557
|
+
|
|
558
|
+
Типы экспортируются из пакета: `BaseItemProps`, `AccordionItemProps`, `NextListItemProps`, `GroupItemProps`, `GroupSelectItemProps`. Дискриминаторы — `isBaseItemProps`, `isAccordionItemProps` и пр.
|
|
559
|
+
|
|
560
|
+
### Controlled vs uncontrolled
|
|
561
|
+
|
|
562
|
+
`selection`, `collapse` и `search` принимают одинаковую форму:
|
|
563
|
+
|
|
564
|
+
- **Uncontrolled** — передавайте `defaultValue`. Компонент хранит state сам, пригодно для форм/настроек, где значения потом читаются через `onChange`.
|
|
565
|
+
- **Controlled** — `value` + `onChange`. Нужен, когда state живёт в URL / query / внешнем сторе, или когда требуется программно менять выбор/раскрытие.
|
|
566
|
+
|
|
567
|
+
### Виртуализация
|
|
568
|
+
|
|
569
|
+
- Включайте `virtualized` при размере `items` от ~1000 элементов.
|
|
570
|
+
- `scrollToSelectedItem` работает и с `virtualized`: список прокручивается до выбранной строки при открытии.
|
|
571
|
+
- Внутри `Droplist` виртуализация работает так же — проп `virtualized` доступен и там.
|
|
572
|
+
|
|
573
|
+
### Доступность
|
|
574
|
+
|
|
575
|
+
- Контейнер имеет роль `menu`, элементы — `menuitem`. Это согласовано с использованием списка внутри dropdown/menu-сценариев.
|
|
576
|
+
- Клавиатура: `ArrowDown` / `ArrowUp` переходят между элементами (без wrap-around, пропуская `disabled`), `ArrowRight` раскрывает группу или next-list, `ArrowLeft` сворачивает, `Tab` уводит фокус с item на корневой контейнер и далее из списка. `Enter` / `Space` на сфокусированном элементе выбирают его.
|
|
577
|
+
- Первый `ArrowDown` от корневого контейнера переводит фокус на первый элемент; дальше навигация идёт по элементам.
|
|
578
|
+
- Marker и фоновая заливка выбранного элемента — два независимых носителя смысла, состояние понятно без цвета.
|
|
579
|
+
- Disabled-элементы не попадают в цепочку табуляции и не реагируют на клавиатуру.
|
|
580
|
+
|
|
581
|
+
## Droplist
|
|
582
|
+
|
|
583
|
+
List в popover. Оборачивает триггер (кнопку / поле) и показывает список рядом с ним. Принимает почти все пропсы List.
|
|
584
|
+
|
|
585
|
+
Тот же `List` в popover. `Droplist` оборачивает `children`-триггер, сам управляет открытием/закрытием и показывает список рядом с триггером. Почти все пропсы `List` (`items`, `selection`, `collapse`, `search`, `virtualized`, `pinTop` / `pinBottom`) доступны здесь напрямую.
|
|
586
|
+
|
|
587
|
+
### Когда использовать
|
|
588
|
+
|
|
589
|
+
- Селектор-значение у кнопки/поля (валюта, язык, регион, сортировка).
|
|
590
|
+
- Меню действий (у toolbar-кнопки, у строки таблицы).
|
|
591
|
+
- Вторичная навигация, которую не хочется держать на странице постоянно.
|
|
592
|
+
|
|
593
|
+
Когда **не** нужен Droplist:
|
|
594
|
+
|
|
595
|
+
- Простое меню из 2–3 действий — проще держать inline.
|
|
596
|
+
- Список должен быть виден всегда (sidebar) — используйте **List**.
|
|
597
|
+
- Сложная форма с несколькими полями — используйте **Popover** + свой layout.
|
|
598
|
+
|
|
599
|
+
### Анатомия
|
|
600
|
+
|
|
601
|
+
#### Size (default `s`)
|
|
602
|
+
|
|
603
|
+
Размер задаёт высоту строки списка (Figma `listItem`): `s` = 40px, `m` = 52px, `l` = 66px. Совпадает с `size` у **List**.
|
|
604
|
+
|
|
605
|
+
- `s` — дефолт. Компактные селекторы у кнопок toolbar/header.
|
|
606
|
+
- `m` — списки объектов с описанием.
|
|
607
|
+
- `l` — крупные меню, mobile.
|
|
608
|
+
|
|
609
|
+
#### Selection mode (default off)
|
|
610
|
+
|
|
611
|
+
Режим выбора идентичен **List**:
|
|
612
|
+
|
|
613
|
+
- без `selection` — клик = навигация/действие, состояния «выбран» нет.
|
|
614
|
+
- `selection={{ mode: 'single' }}` — один выбранный элемент (`value: ItemId`); обычно с `closeDroplistOnItemClick`.
|
|
615
|
+
- `selection={{ mode: 'multiple' }}` — множественный выбор (`value: ItemId[]`); popover не закрывается на клик.
|
|
616
|
+
|
|
617
|
+
#### Placement (default `bottom-start`)
|
|
618
|
+
|
|
619
|
+
- `bottom-start` — дефолт. Для списков у кнопки в header/toolbar, анкорится по левому краю триггера.
|
|
620
|
+
- `bottom-end` — если триггер прижат к правому краю контейнера.
|
|
621
|
+
- `top-*` — намеренно ставьте только когда триггер физически у нижнего края страницы. У нижнего края viewport fallback отработает автоматически.
|
|
622
|
+
|
|
623
|
+
#### Trigger event (default `click`)
|
|
624
|
+
|
|
625
|
+
- `click` — дефолт. Предсказуемо, работает на touch, доступнее для клавиатуры.
|
|
626
|
+
- `hover` — допустимо только для навигационных меню без чувствительных действий. Не используйте для селекторов и действий с последствиями.
|
|
627
|
+
|
|
628
|
+
#### Width strategy (default `auto`)
|
|
629
|
+
|
|
630
|
+
- `widthStrategy='auto'` — дефолт. Ширина popover'а по контенту.
|
|
631
|
+
- `widthStrategy='eq'` — popover ровно по ширине триггера. Идеально для селектов в форме.
|
|
632
|
+
- `widthStrategy='gte'` — popover не уже триггера, но может быть шире по контенту.
|
|
633
|
+
|
|
634
|
+
#### Header / Footer
|
|
635
|
+
|
|
636
|
+
Шапка и подвал popover'а — кастомные слоты вокруг тела списка (Figma `dropdownContainer.topBar` / `bottomBar`):
|
|
637
|
+
|
|
638
|
+
- `header` — `ReactNode` над списком (и над полем поиска, если оно есть). Заголовок раздела, справочный блок.
|
|
639
|
+
- `headerDivider` — рисует разделитель между `header` (вместе с полем поиска) и телом списка.
|
|
640
|
+
- `footer` — `ReactNode` под списком. Сводка, ссылка на полный список.
|
|
641
|
+
- `footerDivider` — рисует разделитель между телом списка и `footer`.
|
|
642
|
+
|
|
643
|
+
Разделители включаются только вместе с соответствующим слотом: `headerDivider` без `header` ничего не рисует.
|
|
644
|
+
|
|
645
|
+
#### Close after selection
|
|
646
|
+
|
|
647
|
+
- В `single` + навигация → `closeDroplistOnItemClick` обычно `true`.
|
|
648
|
+
- В `multiple` → всегда `false` (по умолчанию), иначе пользователь не сможет проставить несколько галочек.
|
|
649
|
+
|
|
650
|
+
### Примеры использования
|
|
651
|
+
|
|
652
|
+
#### Селектор-кнопка
|
|
653
|
+
|
|
654
|
+
Кнопка-триггер + Droplist с single selection и закрытием после выбора.
|
|
655
|
+
|
|
656
|
+
```tsx
|
|
657
|
+
import { Button } from '@cloud-ru/ds-button';
|
|
658
|
+
import { Droplist } from '@cloud-ru/ds-list';
|
|
659
|
+
import { useState } from 'react';
|
|
660
|
+
|
|
661
|
+
import styles from './styles.module.scss';
|
|
662
|
+
|
|
663
|
+
export function BasicDroplist() {
|
|
664
|
+
const [value, setValue] = useState<string | number | undefined>('rub');
|
|
665
|
+
|
|
666
|
+
return (
|
|
667
|
+
<div className={styles.wrapper}>
|
|
668
|
+
<Droplist
|
|
669
|
+
trigger='click'
|
|
670
|
+
placement='bottom-start'
|
|
671
|
+
closeDroplistOnItemClick
|
|
672
|
+
selection={{ mode: 'single', value, onChange: setValue }}
|
|
673
|
+
items={[
|
|
674
|
+
{ id: 'usd', content: { label: 'USD — Доллар США' } },
|
|
675
|
+
{ id: 'eur', content: { label: 'EUR — Евро' } },
|
|
676
|
+
{ id: 'rub', content: { label: 'RUB — Российский рубль' } },
|
|
677
|
+
{ id: 'cny', content: { label: 'CNY — Китайский юань' } },
|
|
678
|
+
]}
|
|
679
|
+
>
|
|
680
|
+
<Button size='s' appearance='neutral' view='outline' label={`Валюта: ${String(value).toUpperCase()}`} />
|
|
681
|
+
</Droplist>
|
|
682
|
+
</div>
|
|
683
|
+
);
|
|
684
|
+
}
|
|
685
|
+
```
|
|
686
|
+
|
|
687
|
+
#### Multiple selection
|
|
688
|
+
|
|
689
|
+
Несколько отметок без закрытия. closeDroplistOnItemClick по умолчанию false.
|
|
690
|
+
|
|
691
|
+
```tsx
|
|
692
|
+
import { Button } from '@cloud-ru/ds-button';
|
|
693
|
+
import { Droplist } from '@cloud-ru/ds-list';
|
|
694
|
+
import { useState } from 'react';
|
|
695
|
+
|
|
696
|
+
import styles from './styles.module.scss';
|
|
697
|
+
|
|
698
|
+
export function DroplistMultiple() {
|
|
699
|
+
const [value, setValue] = useState<(string | number)[]>(['email']);
|
|
700
|
+
|
|
701
|
+
return (
|
|
702
|
+
<div className={styles.wrapper}>
|
|
703
|
+
<Droplist
|
|
704
|
+
trigger='click'
|
|
705
|
+
placement='bottom-start'
|
|
706
|
+
selection={{ mode: 'multiple', value, onChange: setValue }}
|
|
707
|
+
items={[
|
|
708
|
+
{ id: 'email', content: { label: 'Email' } },
|
|
709
|
+
{ id: 'push', content: { label: 'Push-уведомления' } },
|
|
710
|
+
{ id: 'sms', content: { label: 'SMS' } },
|
|
711
|
+
{ id: 'telegram', content: { label: 'Telegram' } },
|
|
712
|
+
]}
|
|
713
|
+
>
|
|
714
|
+
<Button size='s' appearance='neutral' view='outline' label={`Каналы: ${value.length}`} />
|
|
715
|
+
</Droplist>
|
|
716
|
+
</div>
|
|
717
|
+
);
|
|
718
|
+
}
|
|
719
|
+
```
|
|
720
|
+
|
|
721
|
+
#### Поиск внутри Droplist
|
|
722
|
+
|
|
723
|
+
search + фильтрация items на стороне потребителя — поведение идентично List.
|
|
724
|
+
|
|
725
|
+
```tsx
|
|
726
|
+
import { Button } from '@cloud-ru/ds-button';
|
|
727
|
+
import { Droplist } from '@cloud-ru/ds-list';
|
|
728
|
+
import { useMemo, useState } from 'react';
|
|
729
|
+
|
|
730
|
+
import styles from './styles.module.scss';
|
|
731
|
+
|
|
732
|
+
const COUNTRIES = [
|
|
733
|
+
'Австрия',
|
|
734
|
+
'Армения',
|
|
735
|
+
'Беларусь',
|
|
736
|
+
'Бразилия',
|
|
737
|
+
'Германия',
|
|
738
|
+
'Грузия',
|
|
739
|
+
'Индия',
|
|
740
|
+
'Казахстан',
|
|
741
|
+
'Китай',
|
|
742
|
+
'Россия',
|
|
743
|
+
'США',
|
|
744
|
+
'Турция',
|
|
745
|
+
];
|
|
746
|
+
|
|
747
|
+
export function DroplistWithSearch() {
|
|
748
|
+
const [value, setValue] = useState<string | number | undefined>('Россия');
|
|
749
|
+
const [query, setQuery] = useState('');
|
|
750
|
+
|
|
751
|
+
const items = useMemo(
|
|
752
|
+
() =>
|
|
753
|
+
COUNTRIES.filter(name => name.toLowerCase().includes(query.toLowerCase())).map(name => ({
|
|
754
|
+
id: name,
|
|
755
|
+
content: { label: name },
|
|
756
|
+
})),
|
|
757
|
+
[query],
|
|
758
|
+
);
|
|
759
|
+
|
|
760
|
+
return (
|
|
761
|
+
<div className={styles.wrapper}>
|
|
762
|
+
<Droplist
|
|
763
|
+
trigger='click'
|
|
764
|
+
placement='bottom-start'
|
|
765
|
+
closeDroplistOnItemClick
|
|
766
|
+
selection={{ mode: 'single', value, onChange: setValue }}
|
|
767
|
+
search={{ value: query, onChange: setQuery, placeholder: 'Поиск страны' }}
|
|
768
|
+
items={items}
|
|
769
|
+
>
|
|
770
|
+
<Button size='s' appearance='neutral' view='outline' label={`Страна: ${value}`} />
|
|
771
|
+
</Droplist>
|
|
772
|
+
</div>
|
|
773
|
+
);
|
|
774
|
+
}
|
|
775
|
+
```
|
|
776
|
+
|
|
777
|
+
#### Form select (widthStrategy="eq")
|
|
778
|
+
|
|
779
|
+
Popover ровно по ширине триггера — поведение нативного select.
|
|
780
|
+
|
|
781
|
+
```tsx
|
|
782
|
+
import { Button } from '@cloud-ru/ds-button';
|
|
783
|
+
import { Droplist } from '@cloud-ru/ds-list';
|
|
784
|
+
import { useState } from 'react';
|
|
785
|
+
|
|
786
|
+
import styles from './styles.module.scss';
|
|
787
|
+
|
|
788
|
+
export function DroplistAsFormSelect() {
|
|
789
|
+
const [value, setValue] = useState<string | number | undefined>('m');
|
|
790
|
+
|
|
791
|
+
const options = [
|
|
792
|
+
{ id: 's', content: { label: 'Small (1 vCPU, 2 GB RAM)' } },
|
|
793
|
+
{ id: 'm', content: { label: 'Medium (2 vCPU, 4 GB RAM)' } },
|
|
794
|
+
{ id: 'l', content: { label: 'Large (4 vCPU, 8 GB RAM)' } },
|
|
795
|
+
{ id: 'xl', content: { label: 'X-Large (8 vCPU, 16 GB RAM)' } },
|
|
796
|
+
];
|
|
797
|
+
const label = options.find(o => o.id === value)?.content.label ?? 'Выбрать';
|
|
798
|
+
|
|
799
|
+
return (
|
|
800
|
+
<div className={styles.formSelect}>
|
|
801
|
+
<Droplist
|
|
802
|
+
trigger='click'
|
|
803
|
+
placement='bottom-start'
|
|
804
|
+
closeDroplistOnItemClick
|
|
805
|
+
widthStrategy='eq'
|
|
806
|
+
selection={{ mode: 'single', value, onChange: setValue }}
|
|
807
|
+
items={options}
|
|
808
|
+
>
|
|
809
|
+
<Button size='s' appearance='neutral' view='outline' label={label} fullWidth />
|
|
810
|
+
</Droplist>
|
|
811
|
+
</div>
|
|
812
|
+
);
|
|
813
|
+
}
|
|
814
|
+
```
|
|
815
|
+
|
|
816
|
+
#### Шапка и подвал с разделителями
|
|
817
|
+
|
|
818
|
+
header / footer — слоты над и под списком; headerDivider / footerDivider рисуют разделители.
|
|
819
|
+
|
|
820
|
+
```tsx
|
|
821
|
+
import { Button } from '@cloud-ru/ds-button';
|
|
822
|
+
import { Droplist } from '@cloud-ru/ds-list';
|
|
823
|
+
import { useState } from 'react';
|
|
824
|
+
|
|
825
|
+
import styles from './styles.module.scss';
|
|
826
|
+
|
|
827
|
+
export function DroplistWithHeader() {
|
|
828
|
+
const [value, setValue] = useState<string | number | undefined>('relevance');
|
|
829
|
+
|
|
830
|
+
return (
|
|
831
|
+
<div className={styles.wrapper}>
|
|
832
|
+
<Droplist
|
|
833
|
+
trigger='click'
|
|
834
|
+
placement='bottom-start'
|
|
835
|
+
closeDroplistOnItemClick
|
|
836
|
+
selection={{ mode: 'single', value, onChange: setValue }}
|
|
837
|
+
header='Сортировать по'
|
|
838
|
+
headerDivider
|
|
839
|
+
footer='4 варианта сортировки'
|
|
840
|
+
footerDivider
|
|
841
|
+
items={[
|
|
842
|
+
{ id: 'relevance', content: { label: 'Релевантности' } },
|
|
843
|
+
{ id: 'date', content: { label: 'Дате создания' } },
|
|
844
|
+
{ id: 'name', content: { label: 'Имени' } },
|
|
845
|
+
{ id: 'size', content: { label: 'Размеру' } },
|
|
846
|
+
]}
|
|
847
|
+
>
|
|
848
|
+
<Button size='s' appearance='neutral' view='outline' label='Сортировка' />
|
|
849
|
+
</Droplist>
|
|
850
|
+
</div>
|
|
851
|
+
);
|
|
852
|
+
}
|
|
853
|
+
```
|
|
854
|
+
|
|
855
|
+
### Props
|
|
856
|
+
|
|
857
|
+
**DroplistProps**
|
|
858
|
+
|
|
859
|
+
| Prop | Type | Default | Description |
|
|
860
|
+
|------|------|---------|-------------|
|
|
861
|
+
| `actionButton` | `ReactNode` | — | Только mobile (`BottomSheet`): action-кнопка справа в шапке. |
|
|
862
|
+
| `barHideStrategy` | `"leave"` \| `"move"` \| `"never"` \| `"scroll"` | — | Управление скрытием скролл баров: <br/> <br> - `Never` - показывать всегда <br/> <br> - `Leave` - скрывать когда курсор покидает компонент <br/> <br> - `Scroll` - показывать только когда происходит скроллинг <br/> <br> - `Move` - показывать при движении курсора над компонентом |
|
|
863
|
+
| `children` | `ReactNode \| ({onKeyDown}) => ReactNode * Рендер функция принимает аргументы `onKeyDown` - хендлер ввода, для поддержки управления с клавиатуры` | — | Триггер для дроплиста |
|
|
864
|
+
| `className` | `string` | — | CSS-класс |
|
|
865
|
+
| `closeDroplistOnItemClick` | `boolean` | `false` | Закрывать выпадающий список после клика на базовый айтем. <br/> Работает в режимах selection: 'none' \| 'single' |
|
|
866
|
+
| `closeOnPopstate` | `boolean` | — | Закрывать ли поповер при переходе по истории браузера |
|
|
867
|
+
| `collapse` | `CollapseState` | — | Настройки раскрытия элементов |
|
|
868
|
+
| `container` | `RefObject<HTMLElement \| null>` | — | Контейнер портала (ref). Переопределяет `PortalContext` для этого дроплиста <br/> (по аналогии с `container` у Modal/Drawer). По умолчанию — из `PortalContextProvider`. |
|
|
869
|
+
| `contentRender` | `((props: ContentRenderProps) => ReactNode)` | — | Рендер функция основного контента айтема |
|
|
870
|
+
| `data-test-id` | `string` | — | |
|
|
871
|
+
| `dataError` | `boolean` | — | Загрузка данных завершилась ошибкой: показывается `errorDataState` |
|
|
872
|
+
| `dataFiltered` | `boolean` | — | Текущий пустой список — результат поиска/фильтра: показывается `noResultsState` вместо `noDataState` |
|
|
873
|
+
| `errorDataState` | `EmptyStateProps` | — | Экран при ошибке запроса |
|
|
874
|
+
| `footer` | `ReactNode ;` | — | Кастомизируемый элемент в конце списка |
|
|
875
|
+
| `footerActiveElementsRefs` | `RefObject<HTMLElement>[]` | — | Список ссылок на кастомные элементы, помещенные в специальную секцию внизу списка |
|
|
876
|
+
| `footerDivider` | `boolean` | — | Показывать divider между body и footer (Figma `dropdownContainer.dividerWrapper` снизу) |
|
|
877
|
+
| `header` | `ReactNode ;` | — | Кастомизируемый элемент в начале списка — Figma `dropdownContainer.topBar`. <br/> Подходит для заголовка / справочного блока над поиском. |
|
|
878
|
+
| `headerDivider` | `boolean` | — | Показывать divider между header и body (Figma `dropdownContainer.dividerWrapper` сверху) |
|
|
879
|
+
| `items` | `BaseItemWithoutNonGroup` \| `CommonGroupItem` \| `Item` \| `ScrollProps` | — | Основные элементы списка |
|
|
880
|
+
| `label` | `string` | — | Только mobile (`BottomSheet`): заголовок шапки. |
|
|
881
|
+
| `limitedScrollHeight` | `boolean` | — | Ограничить максимальную высоту скролл-контейнера в зависимости от `size` |
|
|
882
|
+
| `listRef` | `RefObject<HTMLElement>` | — | Ссылка на элемент выпадающего списка |
|
|
883
|
+
| `loading` | `boolean` | — | Флаг, отвечающий за состояние загрузки списка |
|
|
884
|
+
| `marker` | `boolean` | — | Отображать ли маркер у выбранного элемента списка |
|
|
885
|
+
| `noDataState` | `EmptyStateProps` | — | Экран при отсутствии данных |
|
|
886
|
+
| `noResultsState` | `EmptyStateProps` | — | Экран при отсутствии результатов поиска или фильтров |
|
|
887
|
+
| `onBackButtonClick` | `(() => void)` | — | Только mobile (`BottomSheet`): callback back-кнопки. |
|
|
888
|
+
| `onOpenChange` | `((isOpen: boolean) => void)` | — | Колбек отображения компонента. Срабатывает при изменении состояния open. |
|
|
889
|
+
| `onScroll` | `OriginalScrollProps` | — | Колбек на скролл прокручиваемого списка |
|
|
890
|
+
| `open` | `boolean` | — | Управляет состоянием показан/не показан. |
|
|
891
|
+
| `pinBottom` | `BaseItemWithoutNonGroup` \| `CommonGroupItem` \| `Item` \| `ScrollProps` | — | Элементы списка, закрепленные снизу |
|
|
892
|
+
| `pinTop` | `BaseItemWithoutNonGroup` \| `CommonGroupItem` \| `Item` \| `ScrollProps` | — | Элементы списка, закрепленные сверху |
|
|
893
|
+
| `placement` | `"bottom"` \| `"bottom-end"` \| `"bottom-start"` \| `"left"` \| `"left-end"` \| `"left-start"` \| `"right"` \| `"right-end"` \| `"right-start"` \| `"top"` \| `"top-end"` \| `"top-start"` | `top` | Положение поповера относительно своего триггера (children). |
|
|
894
|
+
| `scroll` | `boolean` | — | Включить ли скролл для основной части списка |
|
|
895
|
+
| `scrollContainerClassName` | `string` | — | CSS-класс для scroll обертки основного списка айтемов |
|
|
896
|
+
| `scrollContainerRef` | `Ref<HTMLElement>` | — | Ссылка на контейнер, который скроллится |
|
|
897
|
+
| `scrollRef` | `Ref<HTMLElement>` | — | Ссылка на элемент, обозначающий самый конец прокручиваемого списка |
|
|
898
|
+
| `scrollToSelectedItem` | `boolean` | — | Флаг, отвечающий за прокручивание до выбранного элемента |
|
|
899
|
+
| `search` | `SearchState` | — | Настройки поисковой строки |
|
|
900
|
+
| `selection` | `SelectionMultipleState` \| `SelectionSingleState` | — | Настройки выбора элементов. `mode: 'single'` — один выбранный элемент (`value: ItemId`), <br/> `mode: 'multiple'` — множественный выбор (`value: ItemId[]`). Без `selection` выбора нет — <br/> клик вызывает только `onClick` элемента. |
|
|
901
|
+
| `size` | `"l"` \| `"m"` \| `"s"` | — | Размер списка |
|
|
902
|
+
| `slotAfterTitle` | `ReactNode` | — | Только mobile (`BottomSheet`): slot справа от заголовка. |
|
|
903
|
+
| `trigger` | `"click"` \| `"clickAndFocusVisible"` \| `"focus"` \| `"focusVisible"` \| `"hover"` \| `"hoverAndFocus"` \| `"hoverAndFocusVisible"` | — | Условие отображения поповера: <br/> - `click` - открывать по клику <br/> - `hover` - открывать по ховеру <br/> - `focusVisible` - открывать по focus-visible <br/> - `focus` - открывать по фокусу <br/> - `hoverAndFocusVisible` - открывать по ховеру и focus-visible <br/> - `hoverAndFocus` - открывать по ховеру и фокусу <br/> - `clickAndFocusVisible` - открывать по клику и focus-visible |
|
|
904
|
+
| `triggerClassName` | `string` | — | CSS-класс триггера |
|
|
905
|
+
| `triggerElemRef` | `RefObject<HTMLElement>` | — | Ссылка на элемент-триггер для дроплиста |
|
|
906
|
+
| `untouchableScrollbars` | `boolean` | — | Отключает возможность взаимодействовать со скролбарами мышью. |
|
|
907
|
+
| `virtualized` | `boolean` | — | Включить виртуализацию элементов списка. Рекомендуется при количестве элементов от 1000. |
|
|
908
|
+
| `widthStrategy` | `"auto"` \| `"eq"` \| `"gte"` | `auto` | Стратегия управления шириной контейнера поповера <br/> - `auto` - соответствует ширине контента, <br/> - `gte` - Great Than or Equal, равен ширине таргета или больше ее, если контент в поповере шире, <br/> - `eq` - Equal, строго равен ширине таргета. |
|
|
909
|
+
|
|
910
|
+
##### Related types
|
|
911
|
+
|
|
912
|
+
**BaseItemWithoutNonGroup**
|
|
913
|
+
|
|
914
|
+
| Prop | Type | Default | Description |
|
|
915
|
+
|------|------|---------|-------------|
|
|
916
|
+
| `afterContent` | `string \| number \| boolean \| ReactElement<any, string \| JSXElementConstructor<any>> \| Iterable<ReactNode> \| ReactPortal \| null \| undefined` | — | Слот после основного контента |
|
|
917
|
+
| `beforeContent` | `string \| number \| boolean \| ReactElement<any, string \| JSXElementConstructor<any>> \| Iterable<ReactNode> \| ReactPortal \| null \| undefined` | — | Слот до основного контента |
|
|
918
|
+
| `checked` | `boolean \| undefined` | — | Управляемое состояние выбранности айтема |
|
|
919
|
+
| `className` | `string \| undefined` | — | CSS-класс |
|
|
920
|
+
| `content` | `ItemContent` | — | Основной контент айтема |
|
|
921
|
+
| `data-test-id` | `string \| undefined` | — | |
|
|
922
|
+
| `disabled` | `boolean \| undefined` | — | Флаг неактивности элемента |
|
|
923
|
+
| `hidden` | `boolean \| undefined` | — | Скрыть элемент из списка (не рендерится и выпадает из навигации) |
|
|
924
|
+
| `id` | `ItemId` | — | Уникальный идентификатор |
|
|
925
|
+
| `itemRef` | `RefObject<HTMLElement> \| undefined` | — | Ссылка на DOM-элемент айтема |
|
|
926
|
+
| `itemWrapRender` | `((item: ReactNode) => ReactNode) \| undefined` | — | Рендер-обёртка вокруг айтема (например, для проксирования в `Tooltip`/`Link`) |
|
|
927
|
+
| `onBlur` | `((e: FocusEvent<HTMLElement>) => void) \| undefined` | — | Колбек обработки блюра |
|
|
928
|
+
| `onClick` | `((e: MouseEvent<HTMLElement>) => void) \| undefined` | — | Колбек обработки клика |
|
|
929
|
+
| `onFocus` | `((e: FocusEvent<HTMLElement>) => void) \| undefined` | — | Колбек обработки фокуса |
|
|
930
|
+
| `onKeyDown` | `((e: KeyboardEvent<HTMLElement>) => void) \| undefined` | — | Колбек обработки нажатия клавиши |
|
|
931
|
+
| `onMouseDown` | `((e: MouseEvent<HTMLElement>) => void) \| undefined` | — | Колбек обработки нажатия кнопки мыши |
|
|
932
|
+
| `showSwitchIcon` | `boolean \| undefined` | — | Флаг отображения иконки у чекбоксов |
|
|
933
|
+
|
|
934
|
+
**CollapseState**
|
|
935
|
+
|
|
936
|
+
| Prop | Type | Default | Description |
|
|
937
|
+
|------|------|---------|-------------|
|
|
938
|
+
| `defaultValue` | `ItemId` | — | |
|
|
939
|
+
| `onChange` | `((value?: ItemId[]) => void) \| undefined` | — | |
|
|
940
|
+
| `value` | `ItemId` | — | |
|
|
941
|
+
|
|
942
|
+
**CommonGroupItem**
|
|
943
|
+
|
|
944
|
+
| Prop | Type | Default | Description |
|
|
945
|
+
|------|------|---------|-------------|
|
|
946
|
+
| `beforeContent` | `string \| number \| boolean \| ReactElement<any, string \| JSXElementConstructor<any>> \| Iterable<ReactNode> \| ReactPortal \| null \| undefined` | — | Слот иконки слева от label. |
|
|
947
|
+
| `divider` | `boolean \| undefined` | — | Показать разделитель над группой |
|
|
948
|
+
| `groupVariant` | `"subtitle"` \| `"subtitleTertiary"` | — | Визуальный стиль заголовка группы |
|
|
949
|
+
| `hidden` | `boolean \| undefined` | — | Скрыть группу из списка |
|
|
950
|
+
| `label` | `string \| undefined` | — | Заголовок группы |
|
|
951
|
+
| `truncate` | `TruncateStringProps` | — | Настройки усечения длинного заголовка группы |
|
|
952
|
+
|
|
953
|
+
**EmptyStateProps**
|
|
954
|
+
|
|
955
|
+
| Prop | Type | Default | Description |
|
|
956
|
+
|------|------|---------|-------------|
|
|
957
|
+
| `className` | `string \| undefined` | — | Дополнительный класс |
|
|
958
|
+
| `content` | `string \| number \| boolean \| ReactElement<any, string \| JSXElementConstructor<any>> \| Iterable<ReactNode> \| ReactPortal \| null \| undefined` | — | Подзаголовок |
|
|
959
|
+
| `data-test-id` | `string \| undefined` | — | |
|
|
960
|
+
| `footer` | `string \| number \| boolean \| ReactElement<any, string \| JSXElementConstructor<any>> \| Iterable<ReactNode> \| ReactPortal \| null \| undefined` | — | Вложенный контент (например ButtonGroup) |
|
|
961
|
+
| `icon` | `IconPredefinedProps` | — | Иконка |
|
|
962
|
+
|
|
963
|
+
- `Item` = `BaseItem | GroupItem | GroupSelectItem | NextListItem | AccordionItem`
|
|
964
|
+
|
|
965
|
+
**ItemContent**
|
|
966
|
+
|
|
967
|
+
| Prop | Type | Default | Description |
|
|
968
|
+
|------|------|---------|-------------|
|
|
969
|
+
| `caption` | `string \| undefined` | — | |
|
|
970
|
+
| `className` | `string \| undefined` | — | |
|
|
971
|
+
| `data-test-id` | `string \| undefined` | — | |
|
|
972
|
+
| `description` | `string \| undefined` | — | |
|
|
973
|
+
| `disabled` | `boolean \| undefined` | — | |
|
|
974
|
+
| `label` | `string \| number` | — | |
|
|
975
|
+
| `truncate` | `TruncateProps` | — | |
|
|
976
|
+
|
|
977
|
+
- `ItemId` = `string | number`
|
|
978
|
+
|
|
979
|
+
- `OnChangeHandler` = `(value: T) => void`
|
|
980
|
+
|
|
981
|
+
**ScrollProps**
|
|
982
|
+
|
|
983
|
+
| Prop | Type | Default | Description |
|
|
984
|
+
|------|------|---------|-------------|
|
|
985
|
+
| `barHideStrategy` | `"leave"` \| `"move"` \| `"never"` \| `"scroll"` | — | Управление скрытием скролл баров: <br/> <br> - `Never` - показывать всегда <br/> <br> - `Leave` - скрывать когда курсор покидает компонент <br/> <br> - `Scroll` - показывать только когда происходит скроллинг <br/> <br> - `Move` - показывать при движении курсора над компонентом |
|
|
986
|
+
| `onScroll` | `OriginalScrollProps` | — | Колбек на скролл прокручиваемого списка |
|
|
987
|
+
| `scroll` | `boolean \| undefined` | — | Включить ли скролл для основной части списка |
|
|
988
|
+
| `scrollContainerRef` | `Ref<HTMLElement> \| undefined` | — | Ссылка на контейнер, который скроллится |
|
|
989
|
+
| `scrollRef` | `Ref<HTMLElement> \| undefined` | — | Ссылка на элемент, обозначающий самый конец прокручиваемого списка |
|
|
990
|
+
| `untouchableScrollbars` | `boolean \| undefined` | — | Отключает возможность взаимодействовать со скролбарами мышью. |
|
|
991
|
+
|
|
992
|
+
**SearchState**
|
|
993
|
+
|
|
994
|
+
| Prop | Type | Default | Description |
|
|
995
|
+
|------|------|---------|-------------|
|
|
996
|
+
| `loading` | `boolean \| undefined` | — | |
|
|
997
|
+
| `onChange` | `(value: string, e?: ChangeEvent<HTMLInputElement>) => void` | — | |
|
|
998
|
+
| `onKeyDown` | `((e: KeyboardEvent<HTMLElement>) => void) \| undefined` | — | |
|
|
999
|
+
| `placeholder` | `string \| undefined` | — | |
|
|
1000
|
+
| `value` | `string \| undefined` | — | |
|
|
1001
|
+
|
|
1002
|
+
**SelectionMultipleState**
|
|
1003
|
+
|
|
1004
|
+
| Prop | Type | Default | Description |
|
|
1005
|
+
|------|------|---------|-------------|
|
|
1006
|
+
| `defaultValue` | `ItemId` | — | Начальное состояние |
|
|
1007
|
+
| `mode` | `"multiple"` | — | Режим выбора |
|
|
1008
|
+
| `onChange` | `ItemId` \| `OnChangeHandler` | — | Controlled обработчик изменения состояния — получает массив выбранных `ItemId[]` |
|
|
1009
|
+
| `value` | `ItemId` | — | Controlled состояние |
|
|
1010
|
+
|
|
1011
|
+
**SelectionSingleState**
|
|
1012
|
+
|
|
1013
|
+
| Prop | Type | Default | Description |
|
|
1014
|
+
|------|------|---------|-------------|
|
|
1015
|
+
| `defaultValue` | `ItemId` | — | Начальное состояние |
|
|
1016
|
+
| `mode` | `"single"` | — | Режим выбора |
|
|
1017
|
+
| `onChange` | `ItemId` \| `OnChangeHandler` | — | Controlled обработчик изменения состояния — получает выбранный `ItemId` |
|
|
1018
|
+
| `value` | `ItemId` | — | Controlled состояние |
|
|
1019
|
+
|
|
1020
|
+
- `Size` = `"l"` \| `"m"` \| `"s"`
|
|
1021
|
+
|
|
1022
|
+
### Figma
|
|
1023
|
+
|
|
1024
|
+
{/* Droplist делит Figma-файл с List — отдельного droplist/dropdownContainer-узла в FIGMA_NODES нет.
|
|
1025
|
+
Это допустимый fallback (см. figma-integration.md): показываем общий list-узел и item-search. */}
|
|
1026
|
+
### Droplist vs List
|
|
1027
|
+
|
|
1028
|
+
| Ситуация
|
|
1029
|
+
|
|
1030
|
+
### Trigger
|
|
1031
|
+
|
|
1032
|
+
`children` — сам триггер. Поддерживаются две формы:
|
|
1033
|
+
|
|
1034
|
+
- `ReactNode` — просто вложенный элемент (кнопка / поле). Droplist навешивает на него open/close.
|
|
1035
|
+
- `({ onKeyDown }) => ReactNode` — render-prop с `onKeyDown`-хендлером, который нужно передать на триггер для клавиатуры. Используйте, если триггер — кастомный компонент без привычной клавиатурной поддержки.
|
|
1036
|
+
|
|
1037
|
+
### Открытие и контроль
|
|
1038
|
+
|
|
1039
|
+
- **Uncontrolled** — компонент сам управляет `open`, ничего передавать не нужно.
|
|
1040
|
+
- **Controlled** — `open` + `onOpenChange`. Нужен для программного открытия (например, по keyboard shortcut) или синхронизации с URL.
|
|
1041
|
+
- `closeOnPopstate` автоматически закрывает popover при `popstate`-событии — полезно в SPA с router'ом.
|
|
1042
|
+
|
|
1043
|
+
### Selection и collapse
|
|
1044
|
+
|
|
1045
|
+
Всё, что относится к содержимому списка (`items`, `pinTop`, `pinBottom`, `selection`, `collapse`, `search`, `footer`, `virtualized`, `marker`, `size`, `contentRender`), работает так же, как в **List**.
|
|
1046
|
+
|
|
1047
|
+
### Доступность
|
|
1048
|
+
|
|
1049
|
+
- Триггер получает `aria-expanded` и `aria-haspopup='listbox'` автоматически (через `Dropdown`).
|
|
1050
|
+
- При открытии фокус переходит внутрь списка, при закрытии — возвращается к триггеру.
|
|
1051
|
+
- `Escape` закрывает popover, `Tab` переключает на следующий focusable элемент страницы.
|
|
1052
|
+
- Стрелки, `Home` / `End`, `Enter` / `Space` работают так же, как в List.
|
|
1053
|
+
- Цвет не единственный индикатор выбранного значения: используется marker и фоновая заливка.
|
|
1054
|
+
|
|
1055
|
+
### Адаптивность
|
|
1056
|
+
|
|
1057
|
+
`Droplist` — адаптивный компонент с переключением поверхности (surface-swap). Раскладку он берёт из `AdaptiveProvider` (контекст `@cloud-ru/ds-adaptive`); публичный API единый для обеих платформ:
|
|
1058
|
+
|
|
1059
|
+
- **desktop** (по умолчанию) — анкорный popover рядом с триггером.
|
|
1060
|
+
- **mobile** — список рендерится в `BottomSheet` из `@cloud-ru/ds-bottom-sheet` (панель снизу с шапкой и крупными строками size `l`).
|
|
1061
|
+
|
|
1062
|
+
Верстайте под desktop и поставьте один `<AdaptiveProvider>` в корне приложения — mobile-поверхность включается автоматически (desktop-first). Пропа `layoutType` у компонента нет: источник раскладки — только контекст.
|
|
1063
|
+
|
|
1064
|
+
#### Как форсировать платформу
|
|
1065
|
+
|
|
1066
|
+
Форс — только контекстом, не пропом:
|
|
1067
|
+
|
|
1068
|
+
- Поддерево — вложенный провайдер:
|
|
1069
|
+
```tsx
|
|
1070
|
+
import { AdaptiveProvider } from '@cloud-ru/ds-adaptive'
|
|
1071
|
+
|
|
1072
|
+
<AdaptiveProvider layoutType='mobile'>
|
|
1073
|
+
<Droplist items={items}>{trigger}</Droplist>
|
|
1074
|
+
</AdaptiveProvider>
|
|
1075
|
+
```
|
|
1076
|
+
- Отдельный компонент — `withLayoutType` (module-scope, сахар над провайдером):
|
|
1077
|
+
```tsx
|
|
1078
|
+
import { withLayoutType } from '@cloud-ru/ds-adaptive'
|
|
1079
|
+
import { Droplist } from '@cloud-ru/ds-list'
|
|
1080
|
+
|
|
1081
|
+
const MobileDroplist = withLayoutType(Droplist, 'mobile')
|
|
1082
|
+
```
|
|
1083
|
+
|
|
1084
|
+
#### Платформенные пропы
|
|
1085
|
+
|
|
1086
|
+
Часть пропов привязана к одной поверхности и на другой молча игнорируется. Таблица синхронизирована с JSDoc-пометками у `DroplistProps`.
|
|
1087
|
+
|
|
1088
|
+
| Пропы | desktop | mobile |
|
|
1089
|
+
|-------|---------|--------|
|
|
1090
|
+
| `trigger`, `placement`, `widthStrategy`, `triggerElemRef`, `listRef`, `triggerClassName` | используется | игнорируется |
|
|
1091
|
+
| `label`, `actionButton`, `slotAfterTitle`, `onBackButtonClick` | игнорируется | используется |
|
|
1092
|
+
| `items`, `selection`, `collapse`, `search`, `footer`, `headerDivider`, `footerDivider` | используется | используется |
|
|
1093
|
+
| `open`, `onOpenChange`, `closeOnPopstate`, `size` | используется | используется |
|
|
1094
|
+
|
|
1095
|
+
#### Mobile — BottomSheet
|
|
1096
|
+
|
|
1097
|
+
Раскладка форсирована в mobile: по клику триггера список открывается в BottomSheet.
|
|
1098
|
+
|
|
1099
|
+
```tsx
|
|
1100
|
+
import { AdaptiveProvider, LAYOUT_TYPE } from '@cloud-ru/ds-adaptive';
|
|
1101
|
+
import { Button } from '@cloud-ru/ds-button';
|
|
1102
|
+
import { Droplist } from '@cloud-ru/ds-list';
|
|
1103
|
+
import { useState } from 'react';
|
|
1104
|
+
|
|
1105
|
+
import styles from './styles.module.scss';
|
|
1106
|
+
|
|
1107
|
+
export function MobileDroplist() {
|
|
1108
|
+
const [value, setValue] = useState<string | number | undefined>('rub');
|
|
1109
|
+
|
|
1110
|
+
return (
|
|
1111
|
+
<AdaptiveProvider layoutType={LAYOUT_TYPE.Mobile}>
|
|
1112
|
+
<div className={styles.wrapper}>
|
|
1113
|
+
<Droplist
|
|
1114
|
+
label='Валюта'
|
|
1115
|
+
closeDroplistOnItemClick
|
|
1116
|
+
selection={{ mode: 'single', value, onChange: setValue }}
|
|
1117
|
+
items={[
|
|
1118
|
+
{ id: 'usd', content: { label: 'USD — Доллар США' } },
|
|
1119
|
+
{ id: 'eur', content: { label: 'EUR — Евро' } },
|
|
1120
|
+
{ id: 'rub', content: { label: 'RUB — Российский рубль' } },
|
|
1121
|
+
{ id: 'cny', content: { label: 'CNY — Китайский юань' } },
|
|
1122
|
+
]}
|
|
1123
|
+
>
|
|
1124
|
+
<Button size='s' appearance='neutral' view='outline' label={`Валюта: ${String(value).toUpperCase()}`} />
|
|
1125
|
+
</Droplist>
|
|
1126
|
+
</div>
|
|
1127
|
+
</AdaptiveProvider>
|
|
1128
|
+
);
|
|
1129
|
+
}
|
|
1130
|
+
```
|
|
1131
|
+
|
|
1132
|
+
Подробнее о модели адаптивности — [Адаптивность — паттерн](/patterns/adaptive).
|
|
1133
|
+
|
|
1134
|
+
## ItemContent
|
|
1135
|
+
|
|
1136
|
+
Каноничная разметка содержимого элемента списка — label / caption / description с единым truncate-поведением.
|
|
1137
|
+
|
|
1138
|
+
`ItemContent` — каноничная разметка основного контента item'а: заголовок (`label`), мета справа (`caption`), подпись снизу (`description`). Используется как значение поля `item.content`, а также экспортируется отдельно для кастомного рендеринга внутри `contentRender`.
|
|
1139
|
+
|
|
1140
|
+
### Когда использовать
|
|
1141
|
+
|
|
1142
|
+
- Всегда, когда элемент списка можно описать как «заголовок + мета-подпись + короткое описание».
|
|
1143
|
+
- Когда нужен одинаковый truncate-алгоритм во всех списках пакета.
|
|
1144
|
+
- Когда `content` задаётся как объект в `items` — ровно это и есть `ItemContent` под капотом.
|
|
1145
|
+
|
|
1146
|
+
Когда **не** нужен:
|
|
1147
|
+
|
|
1148
|
+
- Совсем кастомный layout внутри item — передавайте в `content` свой `ReactNode`, но тогда единый visual-signal и truncate ложатся на вас.
|
|
1149
|
+
- Элемент с одной иконкой без текста — передавайте `beforeContent` / `afterContent` и оставьте `content` пустым.
|
|
1150
|
+
|
|
1151
|
+
### Анатомия
|
|
1152
|
+
|
|
1153
|
+
#### Slots
|
|
1154
|
+
|
|
1155
|
+
- `label` *(required)* — заголовок item'а (имя файла, название валюты, email-адрес).
|
|
1156
|
+
- `caption` — короткая мета справа (счётчик, дата, badge, shortcut).
|
|
1157
|
+
- `description` — подпись под заголовком (хвост описания, короткая справка, секундный статус).
|
|
1158
|
+
|
|
1159
|
+
Вне `ItemContent`, но рядом (поля item'а):
|
|
1160
|
+
|
|
1161
|
+
- `beforeContent` — иконка / аватар слева.
|
|
1162
|
+
- `afterContent` — слот справа: чеврон / trailing-иконка в иконочном квадрате; произвольный контент (счётчик, badge, tag) рендерится в натуральном размере без обрезки.
|
|
1163
|
+
- `switch: true` — заменяет чекбокс/маркер на `Switch` справа; состояние тумблера берётся из `selection`. Для списков-настроек, где выбор = включение функции. `showSwitchIcon` зарезервирован под иконку тумблера.
|
|
1164
|
+
|
|
1165
|
+
#### Group variant
|
|
1166
|
+
|
|
1167
|
+
У групповых item'ов (`type: 'group'` / `'group-select'`) заголовок оформляется `ItemContent`-независимо, через `groupVariant`:
|
|
1168
|
+
|
|
1169
|
+
- `subtitle` — контрастный подзаголовок секции.
|
|
1170
|
+
- `subtitleTertiary` *(default)* — приглушённый третичный текст.
|
|
1171
|
+
|
|
1172
|
+
#### Truncate
|
|
1173
|
+
|
|
1174
|
+
- Дефолт: `label` — 1 строка, `description` — 2 строки, вариант обрезки — `end` (многоточие в конце).
|
|
1175
|
+
- Переопределяется через `truncate.label`, `truncate.description`, `truncate.variant` (`end` / `middle`).
|
|
1176
|
+
|
|
1177
|
+
### Примеры использования
|
|
1178
|
+
|
|
1179
|
+
99% случаев — `content` передаётся как объект, компонент сам строит `<ItemContent />` под капотом:
|
|
1180
|
+
|
|
1181
|
+
#### content как объект
|
|
1182
|
+
|
|
1183
|
+
item.content = { label, caption, description } — List оборачивает в ItemContent сам.
|
|
1184
|
+
|
|
1185
|
+
```tsx
|
|
1186
|
+
import { List } from '@cloud-ru/ds-list';
|
|
1187
|
+
|
|
1188
|
+
import styles from './styles.module.scss';
|
|
1189
|
+
|
|
1190
|
+
export function BasicList() {
|
|
1191
|
+
return (
|
|
1192
|
+
<div className={styles.box}>
|
|
1193
|
+
<List
|
|
1194
|
+
size='s'
|
|
1195
|
+
items={[
|
|
1196
|
+
{ id: 'inbox', content: { label: 'Входящие', caption: '12' } },
|
|
1197
|
+
{ id: 'sent', content: { label: 'Отправленные' } },
|
|
1198
|
+
{ id: 'archive', content: { label: 'Архив', caption: '238' } },
|
|
1199
|
+
{ id: 'trash', content: { label: 'Корзина', description: 'Удаляется через 30 дней' } },
|
|
1200
|
+
]}
|
|
1201
|
+
/>
|
|
1202
|
+
</div>
|
|
1203
|
+
);
|
|
1204
|
+
}
|
|
1205
|
+
```
|
|
1206
|
+
|
|
1207
|
+
#### Слоты beforeContent / afterContent
|
|
1208
|
+
|
|
1209
|
+
Иконка слева, caption-счётчик справа от заголовка и trailing-иконка в afterContent.
|
|
1210
|
+
|
|
1211
|
+
```tsx
|
|
1212
|
+
import { FileSVG, FolderSVG, StarSVG } from '@cloud-ru/ds-icons/interface/system';
|
|
1213
|
+
import { List } from '@cloud-ru/ds-list';
|
|
1214
|
+
|
|
1215
|
+
import styles from './styles.module.scss';
|
|
1216
|
+
|
|
1217
|
+
export function ItemContentSlots() {
|
|
1218
|
+
return (
|
|
1219
|
+
<div className={styles.box}>
|
|
1220
|
+
<List
|
|
1221
|
+
size='m'
|
|
1222
|
+
items={[
|
|
1223
|
+
{
|
|
1224
|
+
id: 'reports',
|
|
1225
|
+
beforeContent: <FolderSVG />,
|
|
1226
|
+
content: { label: 'Отчёты', caption: '24' },
|
|
1227
|
+
afterContent: <StarSVG />,
|
|
1228
|
+
},
|
|
1229
|
+
{
|
|
1230
|
+
id: 'invoice',
|
|
1231
|
+
beforeContent: <FileSVG />,
|
|
1232
|
+
content: { label: 'invoice-2024.pdf', caption: '1.2 МБ' },
|
|
1233
|
+
},
|
|
1234
|
+
]}
|
|
1235
|
+
/>
|
|
1236
|
+
</div>
|
|
1237
|
+
);
|
|
1238
|
+
}
|
|
1239
|
+
```
|
|
1240
|
+
|
|
1241
|
+
#### Заголовок, caption и description вместе
|
|
1242
|
+
|
|
1243
|
+
label + caption (мета справа) + description (подпись снизу) в одном item.
|
|
1244
|
+
|
|
1245
|
+
```tsx
|
|
1246
|
+
import { List } from '@cloud-ru/ds-list';
|
|
1247
|
+
|
|
1248
|
+
import styles from './styles.module.scss';
|
|
1249
|
+
|
|
1250
|
+
export function ItemContentDescription() {
|
|
1251
|
+
return (
|
|
1252
|
+
<div className={styles.box}>
|
|
1253
|
+
<List
|
|
1254
|
+
size='l'
|
|
1255
|
+
items={[
|
|
1256
|
+
{
|
|
1257
|
+
id: 'eu-west',
|
|
1258
|
+
content: {
|
|
1259
|
+
label: 'eu-west-1',
|
|
1260
|
+
caption: 'Доступно',
|
|
1261
|
+
description: 'Ирландия — основной регион размещения',
|
|
1262
|
+
},
|
|
1263
|
+
},
|
|
1264
|
+
{
|
|
1265
|
+
id: 'us-east',
|
|
1266
|
+
content: {
|
|
1267
|
+
label: 'us-east-1',
|
|
1268
|
+
caption: 'Деградация',
|
|
1269
|
+
description: 'Северная Виргиния — повышенная задержка отклика',
|
|
1270
|
+
},
|
|
1271
|
+
},
|
|
1272
|
+
]}
|
|
1273
|
+
/>
|
|
1274
|
+
</div>
|
|
1275
|
+
);
|
|
1276
|
+
}
|
|
1277
|
+
```
|
|
1278
|
+
|
|
1279
|
+
#### Обрезка текста (truncate)
|
|
1280
|
+
|
|
1281
|
+
truncate.label/description задают число строк, variant — место многоточия (end/middle). В узком контейнере длинный текст обрезается.
|
|
1282
|
+
|
|
1283
|
+
```tsx
|
|
1284
|
+
import { List } from '@cloud-ru/ds-list';
|
|
1285
|
+
|
|
1286
|
+
import styles from './styles.module.scss';
|
|
1287
|
+
|
|
1288
|
+
export function ItemContentTruncate() {
|
|
1289
|
+
return (
|
|
1290
|
+
<div className={styles.narrowBox}>
|
|
1291
|
+
<List
|
|
1292
|
+
size='m'
|
|
1293
|
+
items={[
|
|
1294
|
+
{
|
|
1295
|
+
id: 'truncated',
|
|
1296
|
+
content: {
|
|
1297
|
+
label: 'very-long-instance-name-that-overflows.example.internal',
|
|
1298
|
+
caption: '8',
|
|
1299
|
+
description:
|
|
1300
|
+
'Длинное описание ресурса, которое не помещается в одну строку и обрезается по заданному числу строк',
|
|
1301
|
+
truncate: { label: 1, description: 2, variant: 'middle' },
|
|
1302
|
+
},
|
|
1303
|
+
},
|
|
1304
|
+
]}
|
|
1305
|
+
/>
|
|
1306
|
+
</div>
|
|
1307
|
+
);
|
|
1308
|
+
}
|
|
1309
|
+
```
|
|
1310
|
+
|
|
1311
|
+
Если нужен кастомный слот (бейдж, превью, аватар) поверх стандартной разметки — используйте `contentRender` на `List`. Внутри возвращайте `<ItemContent {...props} />` плюс свои дополнения. Это сохраняет типографику и truncate.
|
|
1312
|
+
|
|
1313
|
+
### Props
|
|
1314
|
+
|
|
1315
|
+
**ItemContentProps**
|
|
1316
|
+
|
|
1317
|
+
| Prop | Type | Default | Description |
|
|
1318
|
+
|------|------|---------|-------------|
|
|
1319
|
+
| `caption` | `string` | — | |
|
|
1320
|
+
| `className` | `string` | — | |
|
|
1321
|
+
| `data-test-id` | `string` | — | |
|
|
1322
|
+
| `description` | `string` | — | |
|
|
1323
|
+
| `disabled` | `boolean` | — | |
|
|
1324
|
+
| `label` | `string \| number` | — | |
|
|
1325
|
+
| `truncate` | `TruncateProps` | — | |
|
|
1326
|
+
|
|
1327
|
+
##### Related types
|
|
1328
|
+
|
|
1329
|
+
**TruncateProps**
|
|
1330
|
+
|
|
1331
|
+
| Prop | Type | Default | Description |
|
|
1332
|
+
|------|------|---------|-------------|
|
|
1333
|
+
| `description` | `number \| undefined` | — | |
|
|
1334
|
+
| `label` | `number \| undefined` | — | |
|
|
1335
|
+
| `variant` | `"end"` \| `"middle"` | — | |
|
|
1336
|
+
|
|
1337
|
+
### Storybook
|
|
1338
|
+
|
|
1339
|
+
`ItemContent` живёт внутри `List`, отдельной истории нет — все сценарии видны в **List playground**.
|
|
1340
|
+
|
|
1341
|
+
### Доступность
|
|
1342
|
+
|
|
1343
|
+
- `label` остаётся текстовым заголовком — ассистивные технологии прочитают его как основное имя элемента.
|
|
1344
|
+
- `caption` и `description` находятся в том же DOM-узле и читаются следом за заголовком.
|
|
1345
|
+
- Визуальные truncate-поведения реализованы через CSS `-webkit-line-clamp`, текст не вырезается из DOM и остаётся доступен скринридеру целиком.
|
|
1346
|
+
- Disabled-элементы получают уменьшенный контраст + `data-disabled`, но цвет не единственный сигнал: интерактивность выключается и на уровне родительского item'а.
|
|
1347
|
+
|
|
1348
|
+
## ReorderableList
|
|
1349
|
+
|
|
1350
|
+
```tsx
|
|
1351
|
+
import { ReorderableList, SimpleItem } from '@cloud-ru/ds-list';
|
|
1352
|
+
import { useState } from 'react';
|
|
1353
|
+
|
|
1354
|
+
import styles from './styles.module.scss';
|
|
1355
|
+
|
|
1356
|
+
const INITIAL_ITEMS: SimpleItem[] = [
|
|
1357
|
+
{ id: 'inbox', content: { label: 'Входящие', caption: '12' } },
|
|
1358
|
+
{ id: 'sent', content: { label: 'Отправленные' } },
|
|
1359
|
+
{ id: 'archive', content: { label: 'Архив', caption: '238' } },
|
|
1360
|
+
{ id: 'trash', content: { label: 'Корзина', description: 'Удаляется через 30 дней' } },
|
|
1361
|
+
];
|
|
1362
|
+
|
|
1363
|
+
export function ListReorder() {
|
|
1364
|
+
const [items, setItems] = useState(INITIAL_ITEMS);
|
|
1365
|
+
|
|
1366
|
+
return (
|
|
1367
|
+
<div className={styles.box}>
|
|
1368
|
+
<ReorderableList size='s' items={items} onItemsReorder={setItems} />
|
|
1369
|
+
</div>
|
|
1370
|
+
);
|
|
1371
|
+
}
|
|
1372
|
+
```
|
|
1373
|
+
|
|
1374
|
+
### Props
|
|
1375
|
+
|
|
1376
|
+
| Prop | Type | Default | Description |
|
|
1377
|
+
|------|------|---------|-------------|
|
|
1378
|
+
| `barHideStrategy` | `"leave"` \| `"move"` \| `"never"` \| `"scroll"` | — | Управление скрытием скролл баров: <br/> <br> - `Never` - показывать всегда <br/> <br> - `Leave` - скрывать когда курсор покидает компонент <br/> <br> - `Scroll` - показывать только когда происходит скроллинг <br/> <br> - `Move` - показывать при движении курсора над компонентом |
|
|
1379
|
+
| `className` | `string` | — | CSS-класс |
|
|
1380
|
+
| `collapse` | `CollapseState` | `{}` | Настройки раскрытия элементов |
|
|
1381
|
+
| `contentRender` | `((props: ContentRenderProps) => ReactNode)` | — | Рендер функция основного контента айтема |
|
|
1382
|
+
| `data-test-id` | `string` | — | |
|
|
1383
|
+
| `dataError` | `boolean` | — | Загрузка данных завершилась ошибкой: показывается `errorDataState` |
|
|
1384
|
+
| `dataFiltered` | `boolean` | — | Текущий пустой список — результат поиска/фильтра: показывается `noResultsState` вместо `noDataState` |
|
|
1385
|
+
| `errorDataState` | `EmptyStateProps` | — | Экран при ошибке запроса |
|
|
1386
|
+
| `footer` | `ReactNode ;` | — | Кастомизируемый элемент в конце списка |
|
|
1387
|
+
| `footerActiveElementsRefs` | `RefObject<HTMLElement>[]` | — | Список ссылок на кастомные элементы, помещенные в специальную секцию внизу списка |
|
|
1388
|
+
| `footerDivider` | `boolean` | — | Показывать divider между body и footer (Figma `dropdownContainer.dividerWrapper` снизу) |
|
|
1389
|
+
| `hasListInFocusChain` | `boolean` | `true` | Флаг, отвечающий за включение самого родительского контейнера листа в цепочку фокусирующихся элементов |
|
|
1390
|
+
| `header` | `ReactNode ;` | — | Кастомизируемый элемент в начале списка — Figma `dropdownContainer.topBar`. <br/> Подходит для заголовка / справочного блока над поиском. |
|
|
1391
|
+
| `headerDivider` | `boolean` | — | Показывать divider между header и body (Figma `dropdownContainer.dividerWrapper` сверху) |
|
|
1392
|
+
| `items` | `ReorderItem[]` | `[]` | Основные элементы списка: строки `SimpleItem` и/или группы с заголовком `SimpleGroupItem` <br/> (`type: 'group'` + `label` + сортируемые `items`). |
|
|
1393
|
+
| `keyboardNavigationRef` | `RefObject<{ focusItem(id: ItemId): void; }>` | — | Ссылка на управление навигацией листа с клавиатуры |
|
|
1394
|
+
| `limitedScrollHeight` | `boolean` | — | Ограничить максимальную высоту скролл-контейнера в зависимости от `size` |
|
|
1395
|
+
| `loading` | `boolean` | — | Флаг, отвечающий за состояние загрузки списка |
|
|
1396
|
+
| `marker` | `boolean` | `true` | Отображать ли маркер у выбранного элемента списка |
|
|
1397
|
+
| `noDataState` | `EmptyStateProps` | — | Экран при отсутствии данных |
|
|
1398
|
+
| `noResultsState` | `EmptyStateProps` | — | Экран при отсутствии результатов поиска или фильтров |
|
|
1399
|
+
| `onItemsReorder` | `(items: ReorderItem[]) => void` | — | Колбек по завершению drag&drop-переупорядочивания элементов списка. Список остаётся <br/> управляемым: сам не хранит порядок, а отдаёт наружу целиком обновлённое дерево `items` — <br/> потребитель обновляет свой стейт этим значением. Переупорядочивание работает только среди <br/> «братьев» одного уровня (строки без группы либо строки внутри одной группы; перенос между <br/> группами не поддерживается). |
|
|
1400
|
+
| `onKeyDown` | `((e: KeyboardEvent<HTMLElement>) => void)` | — | Обработчик события по нажатию клавиш |
|
|
1401
|
+
| `onScroll` | `((event?: Event) => void)` | — | Колбек на скролл прокручиваемого списка |
|
|
1402
|
+
| `pinBottom` | `Item[]` | `[]` | Элементы списка, закрепленные снизу |
|
|
1403
|
+
| `pinTop` | `Item[]` | `[]` | Элементы списка, закрепленные сверху |
|
|
1404
|
+
| `scroll` | `boolean` | — | Включить ли скролл для основной части списка |
|
|
1405
|
+
| `scrollContainerClassName` | `string` | — | CSS-класс для scroll обертки основного списка айтемов |
|
|
1406
|
+
| `scrollContainerRef` | `Ref<HTMLElement>` | — | Ссылка на контейнер, который скроллится |
|
|
1407
|
+
| `scrollRef` | `Ref<HTMLElement>` | — | Ссылка на элемент, обозначающий самый конец прокручиваемого списка |
|
|
1408
|
+
| `scrollToSelectedItem` | `boolean` | — | Флаг, отвечающий за прокручивание до выбранного элемента |
|
|
1409
|
+
| `search` | `SearchState` | — | Настройки поисковой строки |
|
|
1410
|
+
| `selection` | `SelectionMultipleState \| SelectionSingleState` | — | Настройки выбора элементов. `mode: 'single'` — один выбранный элемент (`value: ItemId`), <br/> `mode: 'multiple'` — множественный выбор (`value: ItemId[]`). Без `selection` выбора нет — <br/> клик вызывает только `onClick` элемента. |
|
|
1411
|
+
| `size` | `"l"` \| `"m"` \| `"s"` | `m` | Размер списка |
|
|
1412
|
+
| `tabIndex` | `number` | `0` | `tabIndex` корневого элемента списка (для управления порядком фокуса) |
|
|
1413
|
+
| `untouchableScrollbars` | `boolean` | — | Отключает возможность взаимодействовать со скролбарами мышью. |
|