Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(useList): added ability to define initial value to useListState #1483

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 6 additions & 4 deletions src/components/TreeSelect/TreeSelect.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -80,10 +80,12 @@ export const TreeSelect = React.forwardRef(function TreeSelect<T>(
});

const listState = useListState({
expandedById,
disabledById,
activeItemId,
selectedById: selected,
controlledValues: {
expandedById,
disabledById,
activeItemId,
selectedById: selected,
},
});

const setActiveItemId = propsSetActiveItemId ?? listState.setActiveItemId;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ import {ChevronDown, ChevronUp, Database, PlugConnection} from '@gravity-ui/icon
import {Button} from '../../../Button';
import {Icon} from '../../../Icon';
import {Flex, spacing} from '../../../layout';
import {ListItemView, getListParsedState} from '../../../useList';
import type {ListItemCommonProps, ListItemId} from '../../../useList';
import {ListItemView, getListParsedState, useListState} from '../../../useList';
import type {ListItemCommonProps} from '../../../useList';
import {createRandomizedData} from '../../../useList/__stories__/utils/makeData';
import {TreeSelect} from '../../TreeSelect';
import type {TreeSelectProps} from '../../types';
Expand Down Expand Up @@ -40,9 +40,12 @@ export const WithGroupSelectionControlledStateAndCustomIconExample = ({
);

const [value, setValue] = React.useState<string[]>([]);
const [expandedById, setExpanded] = React.useState<Record<ListItemId, boolean>>(
() => getListParsedState(items).initialState.expandedById,
);

const {expandedById, setExpanded} = useListState({
initialValues: {
expandedById: getListParsedState(items).initialState.expandedById,
},
});

return (
<Flex>
Expand Down
64 changes: 45 additions & 19 deletions src/components/useList/__stories__/useList.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -380,6 +380,32 @@ const {
} = useListState();
```

#### props:

```tsx
interface UseListStateProps {
/**
* Initial state values
*/
initialValues?: Partial<ListState>;
/**
* Ability to pass link to another state value
*/
controlledValues?: Partial<ListState>;
}
```

##### controlledValues example:

```tsx
const listState = useListState();

// inside your component
const innerListState = useListState({
controlledValues: listState,
});
```

## Components:

### ListItemView
Expand All @@ -389,33 +415,33 @@ Use it even if the functionality of the `useList` hook seems redundant to you

```tsx
import {
type unstable_ListItemType as ListItemType,
unstable_ListItemView as ListItemView,
type unstable_ListItemType as ListItemType,
unstable_ListItemView as ListItemView,
} from '@gravity-ui/uikit/unstable';

type Entity = {title: stirng, subtitle: string, icon: React.ReactNode};

const items: ListItemType<Entity>[] = [
{title: 'some title 1', subtitle: 'some subtitle 1', icon: <Icon data={Grip} size={16} />},
{title: 'some title 2', subtitle: 'some subtitle 2', icon: <Icon data={Grip} size={16} />},
{title: 'some title 1', subtitle: 'some subtitle 1', icon: <Icon data={Grip} size={16} />},
{title: 'some title 2', subtitle: 'some subtitle 2', icon: <Icon data={Grip} size={16} />},
];

const List = () => {
return (
<>
{items.map(item, i) => {
return (
<ListItemView
key={i}
id={String(i)}
title={item.title}
subtitle={item.subtitle}
endSlot={item.icon}
/>
)
}}
</>
)
return (
<>
{items.map(item, i) => {
return (
<ListItemView
key={i}
id={String(i)}
title={item.title}
subtitle={item.subtitle}
endSlot={item.icon}
/>
)
}}
</>
)
};
```

Expand Down
42 changes: 36 additions & 6 deletions src/components/useList/hooks/useListState.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,49 @@ import React from 'react';

import type {ListState} from '../types';

interface UseListStateProps extends Partial<ListState> {}
interface UseListStateProps {
/**
* Initial state values
*/
initialValues?: Partial<ListState>;
/**
* Ability to pass link to another state value
*
* ```tsx
* const listState = useListState()
*
* // inside your component
* const innerListState = useListState({
* controlledValues: listState
* })
* ```
*/
controlledValues?: Partial<ListState>;
}

function useControlledState<T>(value: T, defaultValue: T) {
const [state, setState] = React.useState(value || defaultValue);

return [value || state, setState] as const;
}

export const useListState = (props: UseListStateProps = {}) => {
const [disabledById, setDisabled] = useControlledState(props.disabledById!, {});
const [selectedById, setSelected] = useControlledState(props.selectedById!, {});
const [expandedById, setExpanded] = useControlledState(props.expandedById!, {});
const [activeItemId, setActiveItemId] = useControlledState(props.activeItemId, undefined);
export const useListState = ({initialValues, controlledValues}: UseListStateProps = {}) => {
const [disabledById, setDisabled] = useControlledState(
controlledValues?.disabledById!,
initialValues?.disabledById || {},
);
const [selectedById, setSelected] = useControlledState(
controlledValues?.selectedById!,
initialValues?.selectedById || {},
);
const [expandedById, setExpanded] = useControlledState(
controlledValues?.expandedById!,
initialValues?.expandedById || {},
);
const [activeItemId, setActiveItemId] = useControlledState(
controlledValues?.activeItemId,
initialValues?.activeItemId,
);

return {
disabledById,
Expand Down
Loading