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(hooks): add use-form hook and changes to related components #14

Merged
merged 2 commits into from
Sep 21, 2023
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
6 changes: 6 additions & 0 deletions .changeset/violet-frogs-travel.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@interlay/ui": patch
"@interlay/hooks": patch
---

feat(hooks): add use-form hook and changes to related components
1 change: 0 additions & 1 deletion .eslintignore
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
*.css
.changeset
dist
scripts/*
*.config.js
.DS_Store
node_modules
Expand Down
2 changes: 1 addition & 1 deletion jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ module.exports = {
]
},
transformIgnorePatterns: ['[/\\\\]node_modules[/\\\\].+\\.(js|jsx)$'],
setupFilesAfterEnv: ['@testing-library/jest-dom/extend-expect', './scripts/setup-test.ts'],
setupFilesAfterEnv: ['./scripts/setup-test.ts'],
testTimeout: 10000,
globals: {
'ts-jest': {
Expand Down
7 changes: 3 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,11 +43,10 @@
"@storybook/react-vite": "^7.4.0",
"@swc/core": "^1.3.84",
"@swc/jest": "^0.2.29",
"@testing-library/dom": "^8.1.0",
"@testing-library/jest-dom": "^5.16.4",
"@testing-library/dom": "^9.3.3",
"@testing-library/jest-dom": "^6.1.3",
"@testing-library/react": "^14.0.0",
"@testing-library/react-hooks": "^8.0.1",
"@testing-library/user-event": "^14.4.3",
"@testing-library/user-event": "^14.5.1",
"@types/jest": "^29.5.4",
"@types/react": "^18.2.21",
"@types/react-dom": "^18.2.7",
Expand Down
6 changes: 4 additions & 2 deletions packages/components/src/Input/BaseInput.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { ValidationState } from '@react-types/shared';
import { forwardRef, InputHTMLAttributes, ReactNode, useEffect, useRef, useState } from 'react';
import { FocusEvent, forwardRef, InputHTMLAttributes, ReactNode, useEffect, useRef, useState } from 'react';
import { Sizes, Spacing } from '@interlay/theme';

import { Field, FieldProps, useFieldProps } from '../Field';
Expand All @@ -22,14 +22,16 @@ type Props = {
padding?: { top?: Spacing; bottom?: Spacing; left?: Spacing; right?: Spacing };
validationState?: ValidationState;
onChange?: (e: React.ChangeEvent<HTMLInputElement>) => void;
onFocus?: (e: FocusEvent<Element>) => void;
onBlur?: (e: FocusEvent<Element>) => void;
};

type NativeAttrs = Omit<InputHTMLAttributes<HTMLInputElement>, keyof Props>;

type InheritAttrs = Omit<
HelperTextProps &
Pick<FieldProps, 'label' | 'labelPosition' | 'labelProps' | 'maxWidth' | 'justifyContent' | 'alignItems'>,
keyof Props & NativeAttrs
keyof (Props & NativeAttrs)
>;
type BaseInputProps = Props & NativeAttrs & InheritAttrs;

Expand Down
1 change: 1 addition & 0 deletions packages/components/src/Input/Input.style.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ const StyledBaseInput = styled.input<BaseInputProps>`
$adornments.bottom ? theme.input.overflow.large.text : theme.input[$size].text};
line-height: ${theme.lineHeight.base};
font-weight: ${({ $size }) => theme.input[$size].weight};
text-overflow: ellipsis;

background-color: ${({ $isDisabled }) => ($isDisabled ? theme.input.disabled.bg : theme.input.background)};
overflow: hidden;
Expand Down
7 changes: 5 additions & 2 deletions packages/components/src/List/List.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,11 @@ type NativeAttrs = Omit<FlexProps, keyof Props>;
type ListProps = Props & NativeAttrs & InheritAttrs;

const List = forwardRef<HTMLUListElement, ListProps>(
({ variant = 'primary', direction = 'column', onSelectionChange, ...props }, ref): JSX.Element => {
const ariaProps = { onSelectionChange, ...props };
(
{ variant = 'primary', direction = 'column', onSelectionChange, selectionMode, selectedKeys, ...props },
ref
): JSX.Element => {
const ariaProps = { onSelectionChange, selectionMode, selectedKeys, ...props };
const state = useListState(ariaProps);
const listRef = useDOMRef<HTMLUListElement>(ref);
const { gridProps } = useGridList(ariaProps, state, listRef);
Expand Down
2 changes: 1 addition & 1 deletion packages/components/src/NumberInput/NumberInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ type InheritAttrs = Omit<
keyof Props | 'errorMessageProps' | 'descriptionProps' | 'disabled' | 'required' | 'readOnly'
>;

type AriaAttrs = Omit<AriaTextFieldOptions<'input'>, (keyof Props & InheritAttrs) | 'onChange'>;
type AriaAttrs = Omit<AriaTextFieldOptions<'input'>, keyof (Props & InheritAttrs)>;

type NumberInputProps = Props & InheritAttrs & AriaAttrs;

Expand Down
6 changes: 3 additions & 3 deletions packages/components/src/Overlay/OpenTransition.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,9 @@ type OpenTransitionProps = Props & InheritAttrs;

const OpenTransition = (props: OpenTransitionProps): any => {
// Do not apply any transition if in chromatic (based on react-spectrum)
// if (process.env.CHROMATIC) {
// return Children.map(props.children, (child) => child && cloneElement(child as any, { isOpen: props.in }));
// }
if (process.env.NODE_ENV === 'test') {
return Children.map(props.children, (child) => child && cloneElement(child as any, { isOpen: props.in }));
}

return (
<Transition timeout={{ enter: 0, exit: 350 }} {...props}>
Expand Down
3 changes: 1 addition & 2 deletions packages/components/src/Select/Select.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,7 @@ export default {
layout: 'centered'
},
args: {
label: 'Coin',
withModal: false
label: 'Coin'
},
render: Render
} as Meta<typeof Select>;
Expand Down
4 changes: 2 additions & 2 deletions packages/components/src/Select/Select.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ const Select = <F extends SelectType = 'listbox', T extends SelectObject = Selec
disabled={disabled}
name={name}
tabIndex={-1}
value={onChange ? state.selectedKey || '' : undefined}
value={onChange ? state.selectedKey.toString() || '' : undefined}
onChange={onChange}
/>
</VisuallyHidden>
Expand All @@ -153,7 +153,7 @@ const Select = <F extends SelectType = 'listbox', T extends SelectObject = Selec
ref={modalRef}
id={modalId}
isOpen={state.isOpen}
listProps={{ selectedKeys: [state.selectedItem?.key], disabledKeys }}
listProps={{ selectedKeys: state.selectedItem?.key ? [state.selectedItem?.key] : [], disabledKeys }}
state={state}
title={modalTitle}
onClose={state.close}
Expand Down
4 changes: 3 additions & 1 deletion packages/components/src/Table/Table.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,9 @@ const Table = forwardRef<HTMLTableElement, TableProps>(
<BaseTable ref={ref} {...props}>
{/* TODO: rename `uid` to `id` */}
<TableHeader columns={columns}>{(column) => <Column key={column.uid}>{column.name}</Column>}</TableHeader>
<TableBody items={rows}>{(item: any) => <Row>{(columnKey) => <Cell>{item[columnKey]}</Cell>}</Row>}</TableBody>
<TableBody items={rows}>
{(item: any) => <Row>{(columnKey) => <Cell>{item[columnKey.toString()]}</Cell>}</Row>}
</TableBody>
</BaseTable>
)
);
Expand Down
52 changes: 48 additions & 4 deletions packages/components/src/TokenInput/TokenInput.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
import { useLabel } from '@react-aria/label';
import { chain, mergeProps, useId } from '@react-aria/utils';
import { forwardRef, Key, ReactNode, useEffect, useState } from 'react';
import { ChangeEvent, FocusEvent, forwardRef, Key, ReactNode, useEffect, useState } from 'react';
import { useDOMRef } from '@interlay/hooks';

import { Flex } from '../Flex';
import { HelperText } from '../HelperText';
import { NumberInput, NumberInputProps } from '../NumberInput';
import { formatUSD } from '../utils/format';
import { triggerChangeEvent } from '../utils/input';

import { TokenAdornment, TokenTicker } from './TokenAdornment';
import { StyledUSDAdornment } from './TokenInput.style';
Expand All @@ -22,6 +21,7 @@ type Props = {
ticker?: TokenTicker;
onClickBalance?: (balance?: string | number) => void;
onChangeTicker?: (ticker?: string) => void;
onValueChange?: (value?: string | number) => void;
selectProps?: Omit<TokenSelectProps, 'label' | 'helperTextId'>;
};

Expand All @@ -43,24 +43,30 @@ const TokenInput = forwardRef<HTMLInputElement, TokenInputProps>(
hidden,
className,
onClickBalance,
onValueChange,
onChangeTicker,
selectProps,
placeholder = '0',
errorMessage,
description,
value: valueProp,
defaultValue,
onBlur,
...props
},
ref
): JSX.Element => {
const inputRef = useDOMRef(ref);

const [value, setValue] = useState(defaultValue);
const [ticker, setTicker] = useState<string>(
(selectProps?.defaultValue as string) || (typeof tickerProp === 'string' ? tickerProp : tickerProp?.text) || ''
);

const { labelProps, fieldProps } = useLabel({ label, ...props });

const selectHelperTextId = useId();
const inputId = useId();

const itemsArr = Array.from(selectProps?.items || []);
const isSelectAdornment = itemsArr.length > 1;
Expand All @@ -72,18 +78,45 @@ const TokenInput = forwardRef<HTMLInputElement, TokenInputProps>(
setTicker(selectProps.value as string);
}, [selectProps?.value]);

useEffect(() => {
if (valueProp === undefined) return;

setValue(valueProp);
}, [valueProp]);

const handleClickBalance = () => {
if (!balance) return;

triggerChangeEvent(inputRef, balance);
inputRef.current?.focus();
onClickBalance?.(balance);
setValue(balance);
onValueChange?.(balance);
};

const handleTokenChange = (ticker: Key) => {
onChangeTicker?.(ticker as string);
setTicker(ticker as string);
};

const handleChange = (e: ChangeEvent<HTMLInputElement>) => {
const value = e.target.value;

onValueChange?.(value);
setValue(value);
};

const handleBlur = (e: FocusEvent<Element>) => {
const relatedTargetEl = e.relatedTarget as HTMLButtonElement;

if (!relatedTargetEl) return;

const isTargetingMaxBtn = relatedTargetEl.getAttribute('aria-controls') === inputId;

if (!isTargetingMaxBtn) {
onBlur?.(e);
}
};

// Prioritise Number Input description and error message
const hasSelectHelperText =
!errorMessage && !description && (selectProps?.errorMessage || selectProps?.description);
Expand All @@ -105,13 +138,23 @@ const TokenInput = forwardRef<HTMLInputElement, TokenInputProps>(
) : null;

const hasLabel = !!label || balance !== undefined;
const hasLabelWithBalance = hasLabel && balance !== undefined;

const numberInputProps: Partial<NumberInputProps> = hasLabelWithBalance
? {
id: inputId,
onBlur: handleBlur,
onChange: handleChange
}
: { onBlur, onChange: handleChange };

return (
<Flex className={className} direction='column' gap='spacing0' hidden={hidden} style={style}>
{hasLabel && (
<TokenInputLabel
balance={humanBalance || balance}
balanceLabel={balanceLabel}
inputId={inputId}
isDisabled={isDisabled || !ticker}
ticker={ticker}
onClickBalance={handleClickBalance}
Expand All @@ -137,7 +180,8 @@ const TokenInput = forwardRef<HTMLInputElement, TokenInputProps>(
pattern='^[0-9]*[.,]?[0-9]*$'
placeholder={placeholder}
size='large'
{...mergeProps(props, fieldProps)}
value={value}
{...mergeProps(props, fieldProps, numberInputProps)}
/>
{hasSelectHelperText && (
<HelperText
Expand Down
4 changes: 3 additions & 1 deletion packages/components/src/TokenInput/TokenInputBalance.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
} from './TokenInput.style';

type TokenInputBalanceProps = {
inputId: string;
ticker?: string;
value: string | number;
onClickBalance?: () => void;
Expand All @@ -18,6 +19,7 @@ type TokenInputBalanceProps = {
};

const TokenInputBalance = ({
inputId,
ticker,
value,
onClickBalance,
Expand All @@ -33,7 +35,7 @@ const TokenInputBalance = ({
<dd>
<StyledTokenInputBalanceValue>{ticker ? value : 0}</StyledTokenInputBalanceValue>
</dd>
<CTA disabled={isDisabled} size='x-small' onPress={onClickBalance}>
<CTA aria-controls={inputId} disabled={isDisabled} size='x-small' onPress={onClickBalance}>
MAX
</CTA>
</StyledTokenInputBalanceWrapper>
Expand Down
3 changes: 3 additions & 0 deletions packages/components/src/TokenInput/TokenInputLabel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { Label, LabelProps } from '../Label';
import { TokenInputBalance } from './TokenInputBalance';

type Props = {
inputId: string;
ticker?: string;
balance?: string | number;
balanceLabel?: ReactNode;
Expand All @@ -18,6 +19,7 @@ type InheritAttrs = Omit<LabelProps, keyof Props>;
type TokenInputLabelProps = Props & InheritAttrs;

const TokenInputLabel = ({
inputId,
balance,
balanceLabel,
isDisabled,
Expand All @@ -33,6 +35,7 @@ const TokenInputLabel = ({
{hasLabel && <Label {...props}>{children}</Label>}
{balance !== undefined && (
<TokenInputBalance
inputId={inputId}
isDisabled={isDisabled}
label={balanceLabel}
ticker={ticker}
Expand Down
5 changes: 4 additions & 1 deletion packages/hooks/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,10 @@
"postpack": "clean-package restore"
},
"dependencies": {
"@interlay/theme": "workspace:*"
"@interlay/theme": "workspace:*",
"@react-aria/utils": "^3.19.0",
"formik": "^2.4.5",
"react-use": "^17.4.0"
},
"peerDependencies": {
"react": ">=18",
Expand Down
1 change: 1 addition & 0 deletions packages/hooks/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export { useMediaQuery } from './use-media-query';
export { useStyleProps } from './use-style-props';
export * from './use-form';
export type { MarginProps, StyleProps, UseStylePropsResult, StyledMarginProps } from './use-style-props';
export { useDOMRef } from './use-dom-ref';
Loading