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

fix(fuselage): Some fuselage types #637

Merged
merged 21 commits into from
Feb 8, 2022
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
1 change: 1 addition & 0 deletions packages/eslint-config-alt/typescript/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ module.exports = {
plugins: ['@typescript-eslint', 'prettier'],
rules: {
'@typescript-eslint/ban-ts-ignore': 'off',
'@typescript-eslint/ban-ts-comment': 'warn',
'@typescript-eslint/indent': 'off',
'@typescript-eslint/no-empty-function': 'error',
'@typescript-eslint/no-extra-parens': 'off',
Expand Down
9 changes: 6 additions & 3 deletions packages/fuselage-hooks/src/useAutoFocus.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,15 @@ import { useEffect, useRef, Ref } from 'react';
* @param options - options of the focus request
* @returns the ref which holds the element
* @public
* @deprecated
*/
export const useAutoFocus = (
export const useAutoFocus = <
T extends { focus: (options?: FocusOptions) => void }
>(
isFocused = true,
options?: FocusOptions
): Ref<{ focus: (options?: FocusOptions) => void }> => {
const elementRef = useRef<{ focus: (options?: FocusOptions) => void }>();
): Ref<T> => {
const elementRef = useRef<T>();

const { preventScroll } = options || {};

Expand Down
3 changes: 2 additions & 1 deletion packages/fuselage-ui-kit/src/elements/OverflowElement.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
Options,
Icon,
useCursor,
OptionType,
} from '@rocket.chat/fuselage';
import * as UiKit from '@rocket.chat/ui-kit';
import React, { useRef, useCallback, ReactElement, useMemo } from 'react';
Expand Down Expand Up @@ -49,7 +50,7 @@ const OverflowElement = ({
}, [show]);

const handleSelection = useCallback(
([value]: [unknown, string]) => {
([value]: OptionType) => {
action({ target: { value } });
reset();
hide();
Expand Down
11 changes: 5 additions & 6 deletions packages/fuselage/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -69,11 +69,11 @@
"@babel/plugin-transform-runtime": "^7.16.0",
"@babel/preset-env": "^7.16.0",
"@babel/preset-react": "^7.16.0",
"@rocket.chat/eslint-config-alt": "workspace:packages/eslint-config-alt",
"@rocket.chat/fuselage-hooks": "workspace:packages/fuselage-hooks",
"@rocket.chat/fuselage-polyfills": "workspace:packages/fuselage-polyfills",
"@rocket.chat/icons": "workspace:packages/icons",
"@rocket.chat/prettier-config": "workspace:packages/prettier-config",
"@rocket.chat/eslint-config-alt": "workspace:^",
"@rocket.chat/fuselage-hooks": "workspace:^",
"@rocket.chat/fuselage-polyfills": "workspace:^",
"@rocket.chat/icons": "workspace:^",
"@rocket.chat/prettier-config": "workspace:^",
"@storybook/addon-essentials": "~6.4.18",
"@storybook/addon-jest": "~6.4.18",
"@storybook/addon-links": "~6.4.18",
Expand Down Expand Up @@ -110,7 +110,6 @@
"postcss-logical": "^4.0.2",
"postcss-svg": "^3.0.0",
"prettier": "^2.3.2",
"prop-types": "^15.7.2",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"react-live": "^2.3.0",
Expand Down
42 changes: 14 additions & 28 deletions packages/fuselage/src/components/Accordion/AccordionItem.tsx
Original file line number Diff line number Diff line change
@@ -1,26 +1,24 @@
import { useToggle, useUniqueId } from '@rocket.chat/fuselage-hooks';
import PropTypes from 'prop-types';
import React, { FC, KeyboardEvent, MouseEvent, ReactNode } from 'react';
import React, { FormEvent, KeyboardEvent, MouseEvent, ReactNode } from 'react';

import { Box } from '../Box';
import { Chevron } from '../Chevron';
import { ToggleSwitch } from '../ToggleSwitch';

export const AccordionItem: FC<{
type AccordionItemProps = {
children?: ReactNode;
className?: string;
defaultExpanded?: boolean;
disabled?: boolean;
expanded?: boolean;
tabIndex?: number;
title: ReactNode;
noncollapsible?: boolean;
onToggle?: (
e: MouseEvent<HTMLElement, MouseEvent> | KeyboardEvent<HTMLElement>
) => void;
onToggleEnabled?: (
e: MouseEvent<HTMLElement, MouseEvent> | KeyboardEvent<HTMLElement>
) => void;
}> = function Item({
onToggle?: (e: MouseEvent | KeyboardEvent) => void;
onToggleEnabled?: (e: FormEvent) => void;
};

export const AccordionItem = function Item({
children,
className,
defaultExpanded,
Expand All @@ -32,12 +30,10 @@ export const AccordionItem: FC<{
onToggle,
onToggleEnabled,
...props
}) {
}: AccordionItemProps) {
const [stateExpanded, toggleStateExpanded] = useToggle(defaultExpanded);
const expanded = propExpanded || stateExpanded;
const toggleExpanded = (
event: MouseEvent<HTMLElement, MouseEvent> | KeyboardEvent<HTMLElement>
) => {
const toggleExpanded = (event: MouseEvent | KeyboardEvent) => {
if (onToggle) {
onToggle.call(event.currentTarget, event);
return;
Expand All @@ -51,15 +47,15 @@ export const AccordionItem: FC<{
const titleId = useUniqueId();
const panelId = useUniqueId();

const handleClick = (e: MouseEvent<HTMLElement, MouseEvent>) => {
const handleClick = (e: MouseEvent<HTMLElement>) => {
if (disabled) {
return;
}
e.currentTarget?.blur();
toggleExpanded(e);
};

const handleKeyDown = (event: KeyboardEvent<HTMLElement>) => {
const handleKeyDown = (event: KeyboardEvent) => {
if (disabled || event.currentTarget !== event.target) {
return;
}
Expand All @@ -85,13 +81,13 @@ export const AccordionItem: FC<{
'tabIndex': !disabled ? tabIndex : undefined,
'onClick': handleClick,
'onKeyDown': handleKeyDown,
};
} as const;

const nonCollapsibleProps = {
'aria-disabled': 'true',
'aria-expanded': 'true',
'aria-labelledby': titleId,
};
} as const;

const barProps = noncollapsible ? nonCollapsibleProps : collapsibleProps;

Expand Down Expand Up @@ -135,13 +131,3 @@ export const AccordionItem: FC<{
</Box>
);
};

AccordionItem.propTypes = {
defaultExpanded: PropTypes.bool,
disabled: PropTypes.bool,
expanded: PropTypes.bool,
tabIndex: PropTypes.number,
title: PropTypes.node,
onToggle: PropTypes.func,
onToggleEnabled: PropTypes.func,
};
18 changes: 18 additions & 0 deletions packages/fuselage/src/components/AutoComplete/AutoComplete.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { ElementType, FC } from 'react';

type AutoCompleteProps = {
value: unknown[];
filter: string;
setFilter?: (filter: string) => void;
options?: { label: string; value: unknown }[];
renderItem?: ElementType;
renderSelected?: ElementType;
onChange: (value: unknown, action: 'remove' | undefined) => void;
getLabel?: (option: { label: string; value: unknown }) => string;
getValue?: (option: { label: string; value: unknown }) => unknown;
renderEmpty?: ElementType;
placeholder?: string;
error?: boolean;
disabled?: boolean;
};
export const AutoComplete: FC<AutoCompleteProps>;
Original file line number Diff line number Diff line change
Expand Up @@ -2,92 +2,54 @@ import {
useMutableCallback,
useResizeObserver,
} from '@rocket.chat/fuselage-hooks';
import React, {
useEffect,
useRef,
useMemo,
useState,
ComponentProps,
ElementType,
FC,
} from 'react';
import React, { useEffect, useRef, useMemo, useState } from 'react';

import { Box, PositionAnimated, AnimatedVisibility } from '../Box';
import Chip from '../Chip';
import { Icon } from '../Icon';
import { InputBox } from '../InputBox';
import Margins from '../Margins';
import { useCursor, Options } from '../Options';
import { Option } from '../Options/useCursor';

type OptionValue = string | number;
type OptionType = {
value: OptionValue;
label?: string | number;
};

type AutoCompleteProps = Omit<ComponentProps<typeof Options>, 'options'> & {
value: OptionValue;
filter: string;
setFilter?: (filter: string) => void;
options?: OptionType[];
renderItem?: ElementType;
renderSelected?: ElementType;
onChange: (value: OptionValue, action?: 'remove' | undefined) => void;
getLabel?: (option: OptionType) => string;
getValue?: (option: OptionType) => OptionValue;
renderEmpty?: ElementType;
placeholder?: string;
error?: boolean;
disabled?: boolean;
};

const Addon = (props: ComponentProps<typeof Box>) => (
<Box rcx-autocomplete__addon {...props} />
);
const Addon = (props) => <Box rcx-autocomplete__addon {...props} />;

const SelectedOptions = React.memo((props) => <Chip {...props} />);
export const AutoComplete: FC<AutoCompleteProps> = ({
export function AutoComplete({
value,
filter,
setFilter = () => {},
options = [],
renderItem,
renderSelected: RenderSelected = SelectedOptions,
onChange = () => {},
getLabel = ({ label }) => label,
getLabel = ({ label } = {}) => label,
getValue = ({ value }) => value,
renderEmpty,
placeholder,
error,
disabled,
}) => {
}) {
const { ref: containerRef, borderBoxSize } = useResizeObserver();

const ref = useRef<HTMLElement>(null);
const ref = useRef();

const [selected, setSelected] = useState(() =>
options.find((option) => getValue(option) === value)
);

const index = (selected && options.indexOf(selected)) || 0;

const selectByKeyboard = useMutableCallback(([value]) => {
setSelected(options.find((option) => getValue(option) === value));
onChange(value);
setFilter('');
});

const memoizedOptions = useMemo<[unknown, string, boolean?][]>(
() =>
options.map(
({ label, value }) => [value, label] as [unknown, string, boolean?]
),
const memoizedOptions = useMemo(
() => options.map(({ label, value }) => [value, label]),
[options]
);

const [cursor, handleKeyDown, , reset, [optionsAreVisible, hide, show]] =
useCursor(index, memoizedOptions as Option[], selectByKeyboard);
useCursor(value, memoizedOptions, selectByKeyboard);

const onSelect = useMutableCallback(([value]) => {
setSelected(options.find((option) => getValue(option) === value));
Expand All @@ -102,7 +64,7 @@ export const AutoComplete: FC<AutoCompleteProps> = ({
<Box
rcx-autocomplete
ref={containerRef}
onClick={useMutableCallback(() => ref.current && ref.current.focus())}
onClick={useMutableCallback(() => ref.current.focus())}
flexGrow={1}
className={useMemo(
() => [error && 'invalid', disabled && 'disabled'],
Expand Down Expand Up @@ -171,4 +133,4 @@ export const AutoComplete: FC<AutoCompleteProps> = ({
</PositionAnimated>
</Box>
);
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ it('renders without crashing', () => {
render(
<AutoComplete
filter=''
value={''}
value={[]}
renderItem={() => null}
onChange={jest.fn()}
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,21 +19,30 @@ export default {
export const Example = () => {
const [options, setOptions] = useState([]);
const [filter, setFilter] = useState('');
const [value, setValue] = useState<string | number>('');

const [value, setValue] = useState<unknown[]>([]);

useEffect(() => {
(async () => {
const result = await Promise.resolve([]);
setOptions(result);
})();
}, [filter]);

const handleValue = (value: unknown, action: 'remove' | undefined): void => {
if (action) {
return;
}
setValue([]);
};

return (
<AutoComplete
value={value}
filter={filter}
setFilter={setFilter}
options={options}
onChange={setValue}
onChange={handleValue}
/>
);
};
Loading