Skip to content

Commit

Permalink
feat(hooks): add use-form hook and changes to related components (#14)
Browse files Browse the repository at this point in the history
  • Loading branch information
danielsimao authored Sep 21, 2023
1 parent 0303168 commit 85d810a
Show file tree
Hide file tree
Showing 23 changed files with 883 additions and 123 deletions.
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

0 comments on commit 85d810a

Please sign in to comment.