Skip to content

Commit

Permalink
chore: merge main branch
Browse files Browse the repository at this point in the history
  • Loading branch information
sepehr2github committed Mar 26, 2024
2 parents 7791714 + bd0f4ce commit c6821e2
Show file tree
Hide file tree
Showing 36 changed files with 957 additions and 81 deletions.
7 changes: 6 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,18 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),

### Added

- Implement `DaoDataListItem`, `ProposalDataListItem.Structure`, `AssetDataListItem` and `MemberDataListItem.Structure` module components
- Implement `DaoDataListItem.Structure`, `ProposalDataListItem.Structure`, `MemberDataListItem.Structure`,
`AssetDataListItem.Structure` and `AddressInput` module components
- Implement `StatePingAnimation` core component
- Implement `addressUtils` and `ensUtils` module utilities
- Implement `useDebouncedValue` core hook and `clipboardUtils` core utility

### Changed

- Update `Tag` component primary variant styling
- Update Eslint rules to align usage of boolean properties
- Update default query-client options to set a stale time greater than 0
- Bump `webpack-dev-middleware` from 6.1.1 to 6.1.2

### Fixed

Expand Down
2 changes: 1 addition & 1 deletion jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ const config = {
'^.+\\.svg$': '<rootDir>/src/core/test/svgTransform.js',
'^.+\\.m?[tj]sx?$': 'ts-jest',
},
transformIgnorePatterns: ['node_modules/(?!(.*\\.mjs$|react-merge-refs))'],
transformIgnorePatterns: ['node_modules/(?!(.*\\.mjs$|react-merge-refs|wagmi|@wagmi))'],
};

module.exports = config;
1 change: 1 addition & 0 deletions src/core/components/input/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export * from './hooks';
export * from './inputContainer';
export * from './inputDate';
export * from './inputFileAvatar';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,8 @@ export interface IInputContainerBaseProps {
*/
wrapperClassName?: string;
/**
* Shortcircuits all the input wrapper classes to pass control to the child component.
* Does not render the default input wrapper when set to true, to be used for using the base input container
* properties (label, helpText, ..) for components without a input wrapper (e.g. file inputs).
*/
useCustomWrapper?: boolean;
}
Expand All @@ -75,7 +76,7 @@ export interface IInputContainerProps extends IInputContainerBaseProps, Omit<Com
export type InputComponentElement = HTMLInputElement | HTMLTextAreaElement;

export interface IInputComponentProps<TElement extends InputComponentElement = HTMLInputElement>
extends Omit<IInputContainerBaseProps, 'children' | 'id' | 'inputLength'>,
extends Omit<IInputContainerBaseProps, 'children' | 'id' | 'inputLength' | 'useCustomWrapper'>,
Omit<InputHTMLAttributes<TElement>, 'type'> {
/**
* Classes for the input element.
Expand Down
3 changes: 1 addition & 2 deletions src/core/components/textAreas/textArea/textArea.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import classNames from 'classnames';
import { forwardRef } from 'react';
import { InputContainer, type IInputComponentProps } from '../../input';
import { useInputProps } from '../../input/hooks';
import { InputContainer, useInputProps, type IInputComponentProps } from '../../input';

export interface ITextAreaProps extends IInputComponentProps<HTMLTextAreaElement> {}

Expand Down
1 change: 1 addition & 0 deletions src/core/hooks/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './useDebouncedValue';
1 change: 1 addition & 0 deletions src/core/hooks/useDebouncedValue/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { useDebouncedValue, type IUseDebouncedValueParams, type IUseDebouncedValueResult } from './useDebouncedValue';
34 changes: 34 additions & 0 deletions src/core/hooks/useDebouncedValue/useDebouncedValue.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { act, renderHook } from '@testing-library/react';
import { useDebouncedValue } from './useDebouncedValue';

describe('useDebouncedValue hook', () => {
beforeEach(() => {
jest.useFakeTimers();
});

it('returns the value initialised to the value property and a function to update the debounced value', () => {
const value = 'test-value';
const { result } = renderHook(() => useDebouncedValue(value));
expect(result.current).toEqual([value, expect.any(Function)]);
});

it('debounces the value updates', () => {
const newValue = 'test';
const { result, rerender } = renderHook((value) => useDebouncedValue(value));
expect(result.current[0]).toBeUndefined();

rerender(newValue);
expect(result.current[0]).toBeUndefined();

act(() => jest.runAllTimers());
expect(result.current[0]).toEqual(newValue);
});

it('the returned setter updates the debounced value', () => {
const newValue = 'my-value';
const { result } = renderHook((value) => useDebouncedValue(value));
expect(result.current[0]).toBeUndefined();
act(() => result.current[1](newValue));
expect(result.current[0]).toEqual(newValue);
});
});
39 changes: 39 additions & 0 deletions src/core/hooks/useDebouncedValue/useDebouncedValue.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { useEffect, useRef, useState } from 'react';

export interface IUseDebouncedValueParams {
/**
* Debounce time period in milliseconds.
* @default 500
*/
delay?: number;
}

export type IUseDebouncedValueResult<TValue> = [
/**
* Debounced value.
*/
TValue,
/**
* Setter for the debounced value.
*/
(value: TValue) => void,
];

export const useDebouncedValue = <TValue>(
value: TValue,
params: IUseDebouncedValueParams = {},
): IUseDebouncedValueResult<TValue> => {
const { delay } = params;

const [debouncedValue, setDebouncedValue] = useState(value);

const timeoutRef = useRef<NodeJS.Timeout>();

useEffect(() => {
timeoutRef.current = setTimeout(() => setDebouncedValue(value), delay);

return () => clearTimeout(timeoutRef.current);
}, [value, delay]);

return [debouncedValue, setDebouncedValue];
};
1 change: 1 addition & 0 deletions src/core/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export * from './components';
export * from './hooks';
export * from './types';
export * from './utils';
4 changes: 4 additions & 0 deletions src/core/test/setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,11 @@
// expect(element).toHaveTextContent(/react/i)
// learn more: https://github.com/testing-library/jest-dom
import '@testing-library/jest-dom';
import { TextDecoder, TextEncoder } from 'util';
import { testLogger } from './utils';

// Setup test logger
testLogger.setup();

// Globally setup TextEncoder/TextDecoder needed by viem
Object.assign(global, { TextDecoder, TextEncoder });
61 changes: 61 additions & 0 deletions src/core/utils/clipboardUtils/clipboardUtils.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import { testLogger } from '../../test';
import { clipboardUtils } from './clipboardUtils';

// Navigator.clipboard object is not defined on Jest by default
Object.defineProperty(navigator, 'clipboard', {
value: { writeText: jest.fn(), readText: jest.fn() },
});

describe('clipboard utils', () => {
describe('copy', () => {
const writeTextMock = jest.spyOn(navigator.clipboard, 'writeText');

afterEach(() => {
writeTextMock.mockReset();
});

it('copies the specified value on the user clipboard', async () => {
const copyValue = 'copy-value';
await clipboardUtils.copy(copyValue);
expect(writeTextMock).toHaveBeenCalledWith(copyValue);
});

it('calls the onError callback on copy error', async () => {
testLogger.suppressErrors();
const onError = jest.fn();
const error = new Error('test-error');
writeTextMock.mockImplementation(() => {
throw error;
});
await clipboardUtils.copy('test', { onError });
expect(onError).toHaveBeenCalledWith(error);
});
});

describe('paste', () => {
const readTextMock = jest.spyOn(navigator.clipboard, 'readText');

afterEach(() => {
readTextMock.mockReset();
});

it('reads and returns the user clipboard', async () => {
const clipboardValue = 'test-value';
readTextMock.mockResolvedValue(clipboardValue);
const result = await clipboardUtils.paste();
expect(result).toEqual(clipboardValue);
});

it('calls the onError callback on paste error', async () => {
testLogger.suppressErrors();
const onError = jest.fn();
const error = new Error('test-error');
readTextMock.mockImplementation(() => {
throw error;
});
const result = await clipboardUtils.paste({ onError });
expect(result).toEqual('');
expect(onError).toHaveBeenCalledWith(error);
});
});
});
33 changes: 33 additions & 0 deletions src/core/utils/clipboardUtils/clipboardUtils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
export interface IClipboardUtilsParams {
/**
* Callback called on paste error.
*/
onError?: (error: unknown) => void;
}

class ClipboardUtils {
copy = async (value: string, params: IClipboardUtilsParams = {}): Promise<void> => {
const { onError } = params;

try {
await navigator.clipboard.writeText(value);
} catch (error: unknown) {
onError?.(error);
}
};

paste = async (params: IClipboardUtilsParams = {}): Promise<string> => {
const { onError } = params;
let clipboardText = '';

try {
clipboardText = await navigator.clipboard.readText();
} catch (error: unknown) {
onError?.(error);
}

return clipboardText;
};
}

export const clipboardUtils = new ClipboardUtils();
1 change: 1 addition & 0 deletions src/core/utils/clipboardUtils/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { clipboardUtils, type IClipboardUtilsParams } from './clipboardUtils';
1 change: 1 addition & 0 deletions src/core/utils/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export * from './clipboardUtils';
export * from './formatterUtils';
export * from './mergeRefs';
export * from './responsiveUtils';
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import type { Meta, StoryObj } from '@storybook/react';
import { useState } from 'react';
import { AddressInput, type IAddressInputProps, type IAddressInputResolvedValue } from './addressInput';

const meta: Meta<typeof AddressInput> = {
title: 'Modules/Components/Address/AddressInput',
component: AddressInput,
tags: ['autodocs'],
parameters: {
design: {
type: 'figma',
url: 'https://www.figma.com/file/P0GeJKqILL7UXvaqu5Jj7V/v1.1.0?type=design&node-id=8192-18146&mode=design&t=VfR81DAQucRS3iGm-4',
},
},
};

type Story = StoryObj<typeof AddressInput>;

const ControlledComponent = (props: IAddressInputProps) => {
const [value, setValue] = useState<string>();
const [addressValue, setAddressValue] = useState<IAddressInputResolvedValue>();

const stringAddressValue = JSON.stringify(addressValue, null, 2) ?? 'undefined';

return (
<div className="flex grow flex-col gap-2">
<AddressInput value={value} onChange={setValue} onAccept={setAddressValue} {...props} />
<code className="[word-break:break-word]">Address value: {stringAddressValue}</code>
</div>
);
};

/**
* Default usage of the AddressInput component.
*/
export const Default: Story = {
args: {
placeholder: 'ENS or 0x …',
},
render: ({ onChange, onAccept, ...props }) => <ControlledComponent {...props} />,
};

export default meta;
Loading

0 comments on commit c6821e2

Please sign in to comment.