diff --git a/packages/components/src/components/Tree/Tree.css b/packages/components/src/components/Tree/Tree.css index cfcd23c87..b324d56a7 100644 --- a/packages/components/src/components/Tree/Tree.css +++ b/packages/components/src/components/Tree/Tree.css @@ -49,6 +49,13 @@ } } +.kbq-TreeItem:has([data-slot='list-item-addon']) { + [data-slot='chevron'], + [data-slot='checkbox'] { + margin-inline-end: var(--kbq-size-s); + } +} + .kbq-TreeLoader { display: flex; align-items: center; diff --git a/packages/components/src/components/Tree/Tree.mdx b/packages/components/src/components/Tree/Tree.mdx index 36f8b357d..46881cfc0 100644 --- a/packages/components/src/components/Tree/Tree.mdx +++ b/packages/components/src/components/Tree/Tree.mdx @@ -37,6 +37,8 @@ Use these components to build hierarchical navigation, selection, and lazy loadi - `Tree` — Root container for hierarchical items. - `Tree.Item` — Defines a node in the tree (leaf or branch). - `Tree.ItemContent` — Customizes row content (text, icons, slots). +- `Tree.ItemContentText` — Displays text and an optional caption in an item. +- `Tree.ItemContentAddon` — Displays an icon, badge, or other secondary content in an item. - `Tree.LoadMoreItem` — Triggers and displays async loading state for additional items. ## Content @@ -53,12 +55,23 @@ Use `selectionBehavior="toggle"` to render checkbox selection controls. -## Slots +## Item content -Use `Tree.ItemContent` to customize item layout. -For example, you can add icons, typography, badges, and slot-specific props for chevron and checkbox controls. +The `Tree.ItemContent` can be composed with helper components: - +- `Tree.ItemContentText` for text and captions. +- `Tree.ItemContentAddon` for icons, badges, or other secondary content. + +The `Tree.Item` also have layout props, such as `align`, which sets vertical alignment. +Use `align="start"` for captions or multi-line content. + + + +## Item actions + +Add an extra action to an item, revealed on hover or focus. + + ## Empty state @@ -92,7 +105,3 @@ Tree supports progressive loading with `Tree.LoadMoreItem`. Combine it with `useAsyncList` to fetch nested data on demand. - -## Other examples - - diff --git a/packages/components/src/components/Tree/Tree.stories.tsx b/packages/components/src/components/Tree/Tree.stories.tsx index 03aa1109e..e08bc83df 100644 --- a/packages/components/src/components/Tree/Tree.stories.tsx +++ b/packages/components/src/components/Tree/Tree.stories.tsx @@ -1,11 +1,16 @@ import { useState } from 'react'; -import { IconEllipsisVertical16, IconFolder16 } from '@koobiq/react-icons'; +import { + IconCircle16, + IconEllipsisVertical16, + IconFolder16, +} from '@koobiq/react-icons'; import { Collection } from '@koobiq/react-primitives'; import type { Meta, StoryObj } from '@storybook/react'; -import { FlexBox, useAsyncList } from '../../index'; +import { useAsyncList } from '../../index'; import type { Selection } from '../../index'; +import { Badge } from '../Badge'; import { IconButton } from '../IconButton'; import { spacing } from '../layout'; import { Menu } from '../Menu'; @@ -22,10 +27,12 @@ const meta = { subcomponents: { 'Tree.Item': Tree.Item, 'Tree.ItemContent': Tree.ItemContent, + 'Tree.ItemContentText': Tree.ItemContentText, + 'Tree.ItemContentAddon': Tree.ItemContentAddon, 'Tree.LoadMoreItem': Tree.LoadMoreItem, }, argTypes: {}, - tags: ['status:new', 'date:2026-03-02'], + tags: ['status:updated', 'date:2026-07-03'], } satisfies Meta; export default meta; @@ -222,7 +229,95 @@ export const Content: Story = { }, }; -export const Slots: Story = { +export const ItemContent: Story = { + name: 'Item content', + parameters: { + layout: 'padded', + }, + render: function Render() { + const longText = + 'Lorem ipsum dolor sit amet, consectetur adipisicing elit. A at cupiditate dolor itaque molestias quasi quisquam quo. Deleniti ducimus fugit nulla repudiandae tenetur. Aliquid autem corporis culpa debitis exercitationem inventore labore nihil officia recusandae veniam. Beatae, doloribus, suscipit. Aut beatae consectetur consequuntur cum hic, obcaecati quia sunt temporibus unde vel!'; + + return ( + + + + + + + app + + + + + index.html + + + + + + + + + + + .gitignore + + + Badge + + + + + + + + + + README.md + + + + + + + + + + + {longText} + + + + + + + + + + {longText} + + + Badge + + + + + ); + }, +}; + +export const ItemActions: Story = { + name: 'Item actions', parameters: { layout: 'padded', }, @@ -240,22 +335,28 @@ export const Slots: Story = { {({ isHovered, isFocusVisibleWithin }) => ( <> - {type === 'directory' && } - {title} + {type === 'directory' && ( + + + + )} + {title} {(isHovered || isFocusVisibleWithin || isMenuOpen) && ( ( - - - + + + + + )} > Edit @@ -547,107 +648,3 @@ export const AsyncLoading: Story = { ); }, }; - -export const Examples: Story = { - parameters: { - layout: 'padded', - }, - render: (args) => ( - - - app - - Http - - index.html - - - - Providers - - EventServiceProvider.js - - - - - config - - app.js - - - database.js - - - - - - Lorem ipsum dolor sit amet, consectetur adipisicing elit. Accusamus - asperiores delectus doloremque fugiat illo laudantium nesciunt - omnis. Aliquam, earum, velit? - - - - - - Lorem ipsum dolor sit amet, consectetur adipisicing elit. - Accusamus asperiores delectus doloremque fugiat illo laudantium - nesciunt omnis. Aliquam, earum, velit? - - - - - - .env - - - .gitignore - - - README.md - - - - - - Lorem ipsum dolor sit amet, consectetur adipisicing elit. - Accusamus asperiores delectus doloremque fugiat illo laudantium - nesciunt omnis. Aliquam, earum, velit? - - - Lorem ipsum dolor sit amet, consectetur adipisicing elit. - Accusamus asperiores delectus doloremque fugiat illo laudantium - nesciunt omnis. Aliquam, earum, velit? - - - - - - ), -}; diff --git a/packages/components/src/components/Tree/Tree.tsx b/packages/components/src/components/Tree/Tree.tsx index 4bb946ec0..b55913ca3 100644 --- a/packages/components/src/components/Tree/Tree.tsx +++ b/packages/components/src/components/Tree/Tree.tsx @@ -6,6 +6,7 @@ import { Tree as AriaTree, composeRenderProps } from '@koobiq/react-primitives'; import './Tree.css'; import { utilClasses } from '../../styles/utility'; +import { ListItemAddon, ListItemText } from '../List/components'; import { TreeItem, TreeItemContent, TreeLoadMoreItem } from './components'; @@ -41,6 +42,8 @@ TreeComponent.displayName = 'Tree'; type CompoundedComponent = typeof TreeComponent & { Item: typeof TreeItem; ItemContent: typeof TreeItemContent; + ItemContentText: typeof ListItemText; + ItemContentAddon: typeof ListItemAddon; LoadMoreItem: typeof TreeLoadMoreItem; }; @@ -52,4 +55,6 @@ export const Tree = TreeComponent as CompoundedComponent; TreeComponent.Item = TreeItem; TreeComponent.ItemContent = TreeItemContent; +TreeComponent.ItemContentText = ListItemText; +TreeComponent.ItemContentAddon = ListItemAddon; TreeComponent.LoadMoreItem = TreeLoadMoreItem; diff --git a/packages/components/src/components/Tree/components/TreeItem/TreeItem.tsx b/packages/components/src/components/Tree/components/TreeItem/TreeItem.tsx index a3aab7bea..ebfc97895 100644 --- a/packages/components/src/components/Tree/components/TreeItem/TreeItem.tsx +++ b/packages/components/src/components/Tree/components/TreeItem/TreeItem.tsx @@ -17,12 +17,14 @@ export function TreeItem({ children, className, textValue, + align = 'center', ...props }: TreeItemProps) { return ( clsx('kbq-TreeItem', listItem, textVariant['text-normal'], className) )} diff --git a/packages/components/src/components/Tree/components/TreeItem/types.ts b/packages/components/src/components/Tree/components/TreeItem/types.ts index 132ebcc8e..45f378612 100644 --- a/packages/components/src/components/Tree/components/TreeItem/types.ts +++ b/packages/components/src/components/Tree/components/TreeItem/types.ts @@ -1,4 +1,13 @@ import type { DataAttributeProps } from '@koobiq/react-core'; import type { TreeItemProps as AriaTreeItemProps } from '@koobiq/react-primitives'; -export type TreeItemProps = Partial & DataAttributeProps; +import type { ItemPropAlign } from '../../../Collections'; + +export type TreeItemProps = Partial & + DataAttributeProps & { + /** + * Vertical alignment of the item content. + * @default 'center' + */ + align?: ItemPropAlign; + }; diff --git a/packages/components/src/components/Tree/components/TreeItemContent/TreeItemContent.tsx b/packages/components/src/components/Tree/components/TreeItemContent/TreeItemContent.tsx index 88c5ccd73..2e4c45286 100644 --- a/packages/components/src/components/Tree/components/TreeItemContent/TreeItemContent.tsx +++ b/packages/components/src/components/Tree/components/TreeItemContent/TreeItemContent.tsx @@ -39,7 +39,11 @@ export function TreeItemContent(props: TreeItemContentProps) { /> {selectionBehavior === 'toggle' && selectionMode === 'multiple' && ( - + )} {typeof children === 'function' ? children(renderProps) : children} diff --git a/tools/public_api_guard/components/Tree.api.md b/tools/public_api_guard/components/Tree.api.md index 25b078905..dfbb149b6 100644 --- a/tools/public_api_guard/components/Tree.api.md +++ b/tools/public_api_guard/components/Tree.api.md @@ -10,10 +10,13 @@ import type { ComponentPropsWithRef } from 'react'; import type { CSSProperties } from 'react'; import type { DataAttributeProps } from '@koobiq/react-core'; import type { ElementType } from 'react'; +import type { ExtendableComponentPropsWithRef } from '@koobiq/react-core'; import type { ExtendableProps } from '@koobiq/react-core'; +import { ForwardRefExoticComponent } from 'react'; import { JSX } from 'react/jsx-runtime'; import { PolyForwardComponent } from '@koobiq/react-core'; import type { ReactNode } from 'react'; +import { RefAttributes } from 'react'; import type { TreeItemContentProps as TreeItemContentProps_2 } from '@koobiq/react-primitives'; import type { TreeItemProps as TreeItemProps_2 } from '@koobiq/react-primitives'; import { TreeLoadMoreItemProps as TreeLoadMoreItemProps_2 } from '@koobiq/react-primitives'; @@ -35,6 +38,14 @@ export namespace TreeComponent { Item: typeof TreeItem; var // (undocumented) ItemContent: typeof TreeItemContent; + var // Warning: (ae-forgotten-export) The symbol "ListItemTextProps" needs to be exported by the entry point index.d.ts + // + // (undocumented) + ItemContentText: ForwardRefExoticComponent & RefAttributes>; + var // Warning: (ae-forgotten-export) The symbol "ListItemAddonProps" needs to be exported by the entry point index.d.ts + // + // (undocumented) + ItemContentAddon: ForwardRefExoticComponent & RefAttributes>; var // (undocumented) LoadMoreItem: typeof TreeLoadMoreItem; } @@ -57,7 +68,9 @@ export type TreeItemContentPropSlotProps = { }; // @public (undocumented) -export type TreeItemProps = Partial & DataAttributeProps; +export type TreeItemProps = Partial & DataAttributeProps & { + align?: ItemPropAlign; +}; // @public (undocumented) export function TreeLoadMoreItem(props: TreeLoadMoreItemProps): JSX.Element; @@ -74,6 +87,7 @@ export type TreeProps = TreeProps_2 & { // Warnings were encountered during analysis: // +// packages/components/dist/components/Tree/components/TreeItem/types.d.ts:9:5 - (ae-forgotten-export) The symbol "ItemPropAlign" needs to be exported by the entry point index.d.ts // packages/components/dist/components/Tree/components/TreeItemContent/types.d.ts:6:5 - (ae-forgotten-export) The symbol "IconButtonProps" needs to be exported by the entry point index.d.ts // packages/components/dist/components/Tree/components/TreeItemContent/types.d.ts:7:5 - (ae-forgotten-export) The symbol "CheckboxProps" needs to be exported by the entry point index.d.ts