Skip to content

Commit

Permalink
feat(ui): #1714: finish the AssetSelector UI component (#1771)
Browse files Browse the repository at this point in the history
* fix: storybook asset metadata

* fix: make tailwind work in storybook, add icon to assetSelector

* feat(ui): #1714: update AssetSelectorTrigger to the latest designs

* feat(ui): #1714: implement `ListItem` nested component

* feat(ui): #1714: finish the implementation of the AssetSelector

* fix(ui): #1714: fix selected state of the AssetSelector

* feat(ui): #1714: separate `AssetSelector` from `AssetSelector.Custom`

* chore: changeset

* refactor(ui): #1714: rename `AssetSelectorValue`, export types

* feat(ui): #1714: implement sticky header and the correct height in the dialog

* fix(ui): #1714: update default filtering of the balances and fix ListItem styles

* fix(ui): #1714: export useful helpers from the AssetSelector

---------

Co-authored-by: Atris <[email protected]>
  • Loading branch information
VanishMax and vacekj authored Sep 12, 2024
1 parent ed54cc8 commit b1d4b7d
Show file tree
Hide file tree
Showing 21 changed files with 847 additions and 369 deletions.
5 changes: 5 additions & 0 deletions .changeset/happy-mayflies-destroy.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@penumbra-zone/ui': minor
---

Add `AssetSelector` UI component
10 changes: 9 additions & 1 deletion packages/ui/.storybook/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,15 @@ const config = {
getAbsolutePath('@storybook/addon-links'),
getAbsolutePath('@storybook/addon-essentials'),
getAbsolutePath('@storybook/addon-interactions'),
'@storybook/addon-postcss',
{
name: '@storybook/addon-postcss',
options: {
postcssLoaderOptions: {
// When using postCSS 8
implementation: require('postcss'),
},
},
},
'@storybook/preview-api',
],
framework: {
Expand Down
1 change: 1 addition & 0 deletions packages/ui/.storybook/preview.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { PenumbraUIProvider } from '../src/PenumbraUIProvider';
import { Density } from '../src/Density';
import { Tabs } from '../src/Tabs';
import styled from 'styled-components';
import '../styles/globals.css';

const Column = styled.div`
display: flex;
Expand Down

This file was deleted.

This file was deleted.

100 changes: 0 additions & 100 deletions packages/ui/src/AssetSelector/AssetSelectorDialogContent/index.tsx

This file was deleted.

141 changes: 141 additions & 0 deletions packages/ui/src/AssetSelector/Custom.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
import { ReactNode, useId, useState } from 'react';
import styled from 'styled-components';
import { RadioGroup } from '@radix-ui/react-radio-group';
import { Dialog } from '../Dialog';
import { IsAnimatingProvider } from '../IsAnimatingProvider';
import { getHash } from './shared/helpers.ts';
import { AssetSelectorContext } from './shared/Context.tsx';
import { AssetSelectorSearchFilter } from './SearchFilter.tsx';
import { AssetSelectorTrigger } from './Trigger.tsx';
import { AssetSelectorBaseProps } from './shared/types.ts';

const OptionsWrapper = styled.div`
display: flex;
flex-direction: column;
gap: ${props => props.theme.spacing(1)};
`;

interface ChildrenArguments {
onClose: VoidFunction;
/**
* Takes the `Metadata` or `BalancesResponse` and returns
* a unique key string to be used within map in React
*/
getKeyHash: typeof getHash;
}

export interface AssetSelectorCustomProps extends AssetSelectorBaseProps {
/** A value of the search filter inside the selector dialog */
search?: string;

/** Fires when user inputs the value into the search filter inside the selector dialog */
onSearchChange?: (newValue: string) => void;

/**
* Use children as a function to get assistance with keying
* the `ListItem`s and implement you own closing logic.
*
* Example:
* ```tsx
* <AssetSelector>
* {({ getKeyHash, onClose }) => (
* <>
* {options.map(option => (
* <AssetSelector.ListItem key={getKeyHash(option)} value={option} />
* ))}
* <Button onClick={onClose}>Close</Button>
* </>
* )}
* </AssetSelector>
* ```
* */
children?: ReactNode | ((args: ChildrenArguments) => ReactNode);
}

/**
* A custom version of the `AssetSelector` that lets you customize the contents of the selector dialog.
*
* Use `AssetSelector.ListItem` inside the `AssetSelector.Custom` to render the options
* of the selector. It is up for you to sort or group the options however you want.
*
* Example usage:
*
* ```tsx
* const [value, setValue] = useState<Metadata | BalancesResponse>();
* const [search, setSearch] = useState('');
*
* const filteredOptions = useMemo(
* () => mixedOptions.filter(filterMetadataOrBalancesResponseByText(search)),
* [search],
* );
*
* return (
* <AssetSelector
* value={value}
* search={search}
* onChange={setValue}
* onSearchChange={setSearch}
* >
* {({ getKeyHash }) =>
* filteredOptions.map(option => (
* <AssetSelector.ListItem key={getKeyHash(option)} value={option} />
* ))
* }
* </AssetSelector>
* );
* ```
*/
export const AssetSelectorCustom = ({
value,
onChange,
dialogTitle = 'Select Asset',
actionType,
disabled,
children,
search,
onSearchChange,
}: AssetSelectorCustomProps) => {
const layoutId = useId();

const [isOpen, setIsOpen] = useState(false);

const onClose = () => setIsOpen(false);

return (
<Dialog isOpen={isOpen} onClose={() => setIsOpen(false)}>
<AssetSelectorContext.Provider value={{ onClose, onChange, value }}>
<AssetSelectorTrigger
value={value}
actionType={actionType}
disabled={disabled}
layoutId={layoutId}
key={layoutId}
onClick={() => setIsOpen(true)}
/>

<IsAnimatingProvider>
{props => (
<Dialog.Content
title={dialogTitle}
motion={{ ...props, layoutId }}
key={layoutId}
headerChildren={
!!onSearchChange && (
<AssetSelectorSearchFilter value={search} onChange={onSearchChange} />
)
}
>
<RadioGroup value={value ? getHash(value) : undefined} asChild>
<OptionsWrapper>
{typeof children === 'function'
? children({ onClose, getKeyHash: getHash })
: children}
</OptionsWrapper>
</RadioGroup>
</Dialog.Content>
)}
</IsAnimatingProvider>
</AssetSelectorContext.Provider>
</Dialog>
);
};
Loading

0 comments on commit b1d4b7d

Please sign in to comment.