Skip to content

Commit

Permalink
DTRA-1279 / Kate / Filter [WIP] (#51)
Browse files Browse the repository at this point in the history
* feat: add dropdowm and action sheet

* feat: add apply functionality

* chore: remove unused functionality

* refactor: separate apply logic

* feat: add clear all functionality

* feat: apply filtration logic

* fix: filtration bug

* chore: rename variables

* refactor: extract filtration logic into a util function
  • Loading branch information
kate-deriv authored May 20, 2024
1 parent 8e843d3 commit 4be88d0
Show file tree
Hide file tree
Showing 10 changed files with 486 additions and 5 deletions.
124 changes: 124 additions & 0 deletions packages/trader/src/AppV2/Components/Chip/chip.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
.quill-chip {
display: flex;
align-items: center;
justify-content: center;
border-style: solid;
border-width: var(--component-chip-border-width);
border-color: var(--component-chip-border-color);
border-radius: var(--component-chip-border-radius);
background-color: var(--core-color-solid-slate-50);

&:hover {
background-color: var(--component-chip-bg-hover);
}

&:active {
background-color: var(--component-chip-bg-active);
}

& > svg {
fill: var(--component-chip-icon-default);
}

&[data-state='selected']:hover {
background-color: var(--component-chip-bg-selectedHover);
}

&[data-state='selected']:active {
background-color: var(--component-chip-bg-selectedActive);
}

&[data-state='selected'] {
background-color: var(--component-chip-bg-selected);

& > p {
color: var(--component-chip-item-color-default);
}

& > svg {
fill: var(--component-chip-icon-selected);
}

&:has(.rotate[data-state='open']) {
background-color: var(--component-chip-bg-selectedExpand);
}
}

&:disabled {
pointer-events: none;
user-select: none;

& > p {
color: var(--component-chip-label-color-disabled);
}

& > svg {
fill: var(--component-chip-icon-disabled);
}

&[data-state='selected'] {
background-color: var(--component-chip-bg-selectedDisabled);

& > p,
svg {
color: var(--component-chip-label-color-disabledWhite);
fill: var(--component-chip-icon-disabledWhite);
}
}
}

&__disabled {
&--true {
& > svg {
fill: var(--component-chip-icon-disabled) !important;
}

& > p {
color: var(--component-chip-label-color-disabled) !important;
}
}
}

&__size {
&--sm {
padding-inline: var(--component-chip-spacing-padding-md);
height: var(--component-chip-height-sm);
gap: var(--component-chip-spacing-padding-xs);
}
&--md {
padding-inline: var(--component-chip-spacing-padding-lg);
height: var(--component-chip-height-md);
gap: var(--component-chip-spacing-padding-sm);
}
&--lg {
padding-inline: var(--component-chip-spacing-padding-xl);
height: var(--component-chip-height-lg);
gap: var(--component-chip-spacing-padding-md);
}
}

&__custom-right-padding {
&__size {
&--sm {
padding-inline: var(--component-chip-spacing-padding-md) var(--component-chip-spacing-padding-xs);
}
&--md {
padding-inline: var(--component-chip-spacing-padding-lg) var(--component-chip-spacing-padding-sm);
}
&--lg {
padding-inline: var(--component-chip-spacing-padding-xl) var(--component-chip-spacing-padding-md);
}
}
}
}

.rotate {
transition-property: transform;
transition-timing-function: var(--core-motion-ease-400);
transition-duration: var(--core-motion-duration-200);
transform: rotate(0);

&[data-state='open'] {
transform: rotate(180deg);
}
}
55 changes: 55 additions & 0 deletions packages/trader/src/AppV2/Components/Chip/chip.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import React from 'react';
import { StandaloneChevronDownRegularIcon } from '@deriv/quill-icons';
import './chip.scss';
import clsx from 'clsx';
import { CaptionText, Text } from '@deriv-com/quill-ui';
import { TRegularSizes } from '@deriv-com/quill-ui/dist/types';

export const LabelTextSizes: Record<TRegularSizes, JSX.Element> = {
sm: <CaptionText />,
md: <Text />,
lg: <Text />,
};

type BaseChipProps = Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, 'label'> & {
label?: React.ReactNode;
disabled?: boolean;
isDropdownOpen?: boolean;
dropdown?: boolean;
selected?: boolean;
size?: TRegularSizes;
onClick?: () => void;
};

const Chip = React.forwardRef<HTMLButtonElement, BaseChipProps>(
({ size = 'md', label, dropdown = false, className, selected, isDropdownOpen = false, onClick, ...rest }, ref) => (
<button
onClick={onClick}
className={clsx(
'quill-chip',
`quill-chip__size--${size}`,
dropdown && `quill-chip__custom-right-padding__size--${size}`,
className
)}
data-state={selected ? 'selected' : ''}
ref={ref}
{...rest}
>
{label &&
React.cloneElement(LabelTextSizes[size], {
children: label,
})}
{dropdown && (
<StandaloneChevronDownRegularIcon
width={24}
height={24}
data-state={isDropdownOpen ? 'open' : 'close'}
className='rotate'
/>
)}
</button>
)
);

Chip.displayName = 'Chip';
export default Chip;
4 changes: 4 additions & 0 deletions packages/trader/src/AppV2/Components/Chip/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import Chip from './chip';
import './chip.scss';

export default Chip;
44 changes: 44 additions & 0 deletions packages/trader/src/AppV2/Components/Filter/filter.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
.filter {
&__item {
justify-content: space-between;
width: 100%;
height: var(--core-size-2000);
padding-block: var(--core-spacing-400);

&__wrapper {
padding-block: var(--core-spacing-800);
}
// TODO: Temporary css (until quill fix)
.quill-checkbox__wrapper {
order: 3;
}
}
}

// TODO: Temporary css (until quill fix)
.quill-action-sheet {
&--root {
width: 100%;
}

&--handle-bar--line {
width: var(--core-size-2400);
}

&--header > p,
&--title--icon {
display: none;
}

&--header {
padding-block: var(--core-spacing-400);
}

&--title {
height: var(--core-size-2400);
}

&--footer {
padding-block: var(--core-spacing-800);
}
}
96 changes: 96 additions & 0 deletions packages/trader/src/AppV2/Components/Filter/filter.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
import React from 'react';
import Chip from 'AppV2/Components/Chip';
import { ActionSheet, Checkbox } from '@deriv-com/quill-ui';
import { Localize } from '@deriv/translations';

type TFilter = {
setContractTypeFilter: React.Dispatch<React.SetStateAction<string[]>>;
};

// TODO: Replace mockAvailableContractsList with real data when BE will be ready (send list of all available contracts based on account)
const mockAvailableContractsList = [
{ tradeType: <Localize i18n_default_text='Accumulators' />, id: 'Accumulators' },
{ tradeType: <Localize i18n_default_text='Vanillas' />, id: 'Vanillas' },
{ tradeType: <Localize i18n_default_text='Turbos' />, id: 'Turbos' },
{ tradeType: <Localize i18n_default_text='Multipliers' />, id: 'Multipliers' },
{ tradeType: <Localize i18n_default_text='Rise/Fall' />, id: 'Rise/Fall' },
{ tradeType: <Localize i18n_default_text='Higher/Lower' />, id: 'Higher/Lower' },
{ tradeType: <Localize i18n_default_text='Touch/No touch' />, id: 'Touch/No touch' },
{ tradeType: <Localize i18n_default_text='Matches/Differs' />, id: 'Matches/Differs' },
{ tradeType: <Localize i18n_default_text='Even/Odd' />, id: 'Even/Odd' },
{ tradeType: <Localize i18n_default_text='Over/Under' />, id: 'Over/Under' },
];

const Filter = ({ setContractTypeFilter }: TFilter) => {
const [isDropdownOpen, setIsDropDownOpen] = React.useState(false);
const [changedOptions, setChangedOptions] = React.useState<string[]>([]);

const onDropdownClick = () => {
setIsDropDownOpen(!isDropdownOpen);
};

const onChange = (e: React.ChangeEvent<HTMLInputElement> | React.KeyboardEvent<HTMLSpanElement>) => {
const newSelectedOption = (e.target as EventTarget & HTMLInputElement).id;

if (changedOptions.includes(newSelectedOption)) {
setChangedOptions([...changedOptions.filter(item => item !== newSelectedOption)]);
} else {
setChangedOptions([...changedOptions, newSelectedOption]);
}
};

const onApply = () => {
setContractTypeFilter(changedOptions);
};
const onClearAll = () => {
setContractTypeFilter([]);
setChangedOptions([]);
};

const chipLabelFormatting = () => {
const arrayLength = changedOptions.length;
if (!arrayLength) return <Localize i18n_default_text='All trade types' />;
if (changedOptions.length === 1)
return mockAvailableContractsList.find(type => type.id === changedOptions[0])?.tradeType;
return <Localize i18n_default_text='{{amount}} trade types' values={{ amount: arrayLength }} />;
};

return (
<>
<Chip
label={chipLabelFormatting()}
dropdown
isDropdownOpen={isDropdownOpen}
onClick={onDropdownClick}
selected={!!changedOptions.length}
/>
<ActionSheet.Root isOpen={isDropdownOpen} onClose={() => setIsDropDownOpen(false)} position='left'>
<ActionSheet.Portal>
{/* TODO: Add a PR to Quill with changing type of title (need ReactNode)*/}
<ActionSheet.Header title='Filter by trade types' />
<ActionSheet.Content className='filter__item__wrapper'>
{mockAvailableContractsList.map(({ tradeType, id }) => (
<Checkbox
label={tradeType}
className='filter__item'
key={id}
onChange={onChange}
id={id}
checked={changedOptions.includes(id)}
size='md'
/>
))}
</ActionSheet.Content>
{/* TODO: Add PR to Quill in order to switch off (make optional) ability to close action sheet by clicking on btns */}
<ActionSheet.Footer
primaryAction={{ content: 'Apply', onAction: onApply }}
secondaryAction={{ content: 'Clear All', onAction: onClearAll }}
alignment='vertical'
/>
</ActionSheet.Portal>
</ActionSheet.Root>
</>
);
};

export default Filter;
4 changes: 4 additions & 0 deletions packages/trader/src/AppV2/Components/Filter/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import Filter from './filter';
import './filter.scss';

export default Filter;
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,59 @@ import React from 'react';
import { TPortfolioPosition } from '@deriv/stores/types';
import EmptyMessage from 'AppV2/Components/EmptyMessage';
import { TEmptyMessageProps } from 'AppV2/Components/EmptyMessage/empty-message';
import Filter from 'AppV2/Components/Filter';
import { isHighLow } from '@deriv/shared';

type TPositionsContentProps = Omit<TEmptyMessageProps, 'noMatchesFound'> & {
noMatchesFound?: boolean;
positions?: TPortfolioPosition[];
setContractTypeFilter: React.Dispatch<React.SetStateAction<string[]>>;
};

const PositionsContent = ({ isClosedTab, onRedirectToTrade, positions = [] }: TPositionsContentProps) => {
const noMatchesFound = false; // TODO: Implement noMatchesFound state change based on filter results
//TODO: Implement contract card
const ContractCard = ({
contractType,
purchaseTime,
shortcode,
}: {
contractType?: string;
purchaseTime?: number;
shortcode?: string;
}) => (
<div className='contract-card'>
<div>{contractType}</div>
<div>{purchaseTime}</div>
<div>{`${isHighLow({ shortcode }) ? 'High/Low' : 'Rise/Fall'}`}</div>
</div>
);

const PositionsContent = ({
isClosedTab,
noMatchesFound,
onRedirectToTrade,
positions = [],
setContractTypeFilter,
}: TPositionsContentProps) => {
return (
<div className={`positions-page__${isClosedTab ? 'closed' : 'open'}`}>
<div className='positions-page__container'>
<div className='positions-page__filter__wrapper'>
{(positions.length || (!positions.length && noMatchesFound)) && (
<Filter setContractTypeFilter={setContractTypeFilter} />
)}
</div>
</div>
{positions.length ? (
<></>
<React.Fragment>
{positions.map(({ contract_info }) => (
<ContractCard
contractType={contract_info.contract_type}
purchaseTime={contract_info.purchase_time}
shortcode={contract_info.shortcode}
key={contract_info.purchase_time}
/>
))}
</React.Fragment>
) : (
<EmptyMessage
isClosedTab={isClosedTab}
Expand Down
Loading

0 comments on commit 4be88d0

Please sign in to comment.