Skip to content

Commit

Permalink
refactor(textarea): ♻️ update spinner & memoize
Browse files Browse the repository at this point in the history
  • Loading branch information
navin-moorthy committed Jun 21, 2022
1 parent a514b25 commit 57e2230
Show file tree
Hide file tree
Showing 7 changed files with 189 additions and 58 deletions.
17 changes: 11 additions & 6 deletions src/textarea/Textarea.tsx
Original file line number Diff line number Diff line change
@@ -1,24 +1,29 @@
import * as React from "react";

import { runIfFn } from "../utils";

import { TextareaBase } from "./TextareaBase";
import { TextareaGhost } from "./TextareaGhost";
import { TextareaIcon } from "./TextareaIcon";
import { TextareaProps, useTextareaProps } from "./TextareaProps";
import { TextareaSpinner } from "./TextareaSpinner";
import { TextareaWrapper } from "./TextareaWrapper";

export const Textarea = React.forwardRef<HTMLTextAreaElement, TextareaProps>(
(props, ref) => {
const { wrapperProps, baseProps, iconProps, ghostProps, uiProps } =
useTextareaProps(props);
const { loading, icon, spinner } = uiProps;
const {
wrapperProps,
baseProps,
spinnerProps,
iconProps,
ghostProps,
uiProps,
} = useTextareaProps(props);
const { loading, icon } = uiProps;

return (
<TextareaWrapper {...wrapperProps}>
<TextareaBase ref={ref} {...baseProps} />
{loading ? (
runIfFn(spinner, uiProps)
<TextareaSpinner {...spinnerProps} />
) : icon ? (
<TextareaIcon {...iconProps} />
) : null}
Expand Down
2 changes: 1 addition & 1 deletion src/textarea/TextareaIcon.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ export const useTextareaIcon = createHook<TextareaIconOptions>(
const theme = useTheme("textarea");
const className = cx(
theme.icon.base,
autoSize ? theme.icon.autoSize : "",
autoSize || resize === "none" ? theme.icon.autoSize : "",
size ? theme.size[size]?.icon : "",
disabled
? ""
Expand Down
95 changes: 66 additions & 29 deletions src/textarea/TextareaProps.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
import React from "react";

import { getComponentProps } from "../index";
import { RenderProp, runIfFn, withIconA11y } from "../utils";

import { TextareaBaseProps } from "./TextareaBase";
import { TextareaGhostProps } from "./TextareaGhost";
import { TextareaIconProps } from "./TextareaIcon";
import { TextareaSpinnerProps } from "./TextareaSpinner";
import {
TextareaUIState,
TextareaUIStateProps,
Expand All @@ -14,6 +17,7 @@ import { TextareaWrapperProps } from "./TextareaWrapper";
const componentMap = {
TextareaWrapper: "wrapperProps",
TextareaBase: "baseProps",
TextareaSpinner: "spinnerProps",
TextareaIcon: "iconProps",
TextareaGhost: "ghostProps",
};
Expand Down Expand Up @@ -55,44 +59,76 @@ export const useTextareaProps = ({
const { componentProps } = getComponentProps(componentMap, children, uiProps);

const _icon: TextareaProps["icon"] =
componentProps?.iconProps?.children || icon;
componentProps?.iconProps?.children || uiProps.icon;
const _spinner: TextareaProps["spinner"] =
componentProps?.spinnerProps?.children || uiProps.spinner;

uiProps = { ...uiProps, icon: _icon };
uiProps = { ...uiProps, spinner: _spinner, icon: _icon };

const wrapperProps: TextareaWrapperProps = {
...uiProps,
className,
style,
...componentProps.wrapperProps,
};
const wrapperProps: TextareaWrapperProps = React.useMemo(
() => ({
...uiProps,
className,
style,
...componentProps.wrapperProps,
}),
[className, componentProps.wrapperProps, style, uiProps],
);

const baseProps: TextareaBaseProps = {
...uiProps,
placeholder,
value,
disabled,
...restProps,
...componentProps.baseProps,
};
const baseProps: TextareaBaseProps = React.useMemo(
() => ({
...uiProps,
placeholder,
value,
disabled,
...restProps,
...componentProps.baseProps,
}),
[
componentProps.baseProps,
disabled,
placeholder,
restProps,
uiProps,
value,
],
);

const ghostProps: TextareaGhostProps = {
...uiProps,
disabled,
...restProps,
...componentProps.ghostProps,
};
const spinnerProps: TextareaSpinnerProps = React.useMemo(
() => ({
...uiProps,
disabled,
...componentProps.spinnerProps,
children: runIfFn(uiProps.spinner, uiProps),
}),
[componentProps.spinnerProps, disabled, uiProps],
);

const iconProps: TextareaIconProps = {
...uiProps,
disabled,
...componentProps.iconProps,
children: withIconA11y(runIfFn(uiProps.icon, uiProps)),
};
const iconProps: TextareaIconProps = React.useMemo(
() => ({
...uiProps,
disabled,
...componentProps.iconProps,
children: withIconA11y(runIfFn(uiProps.icon, uiProps)),
}),
[componentProps.iconProps, disabled, uiProps],
);

const ghostProps: TextareaGhostProps = React.useMemo(
() => ({
...uiProps,
disabled,
...restProps,
...componentProps.ghostProps,
}),
[componentProps.ghostProps, disabled, restProps, uiProps],
);

return {
uiProps,
wrapperProps,
baseProps,
spinnerProps,
iconProps,
ghostProps,
};
Expand All @@ -108,7 +144,8 @@ export type TextareaProps = Omit<TextareaBaseProps, "children"> &
export type TextareaPropsReturn = {
wrapperProps: TextareaWrapperProps;
baseProps: TextareaBaseProps;
ghostProps: TextareaGhostProps;
spinnerProps: TextareaSpinnerProps;
iconProps: TextareaIconProps;
ghostProps: TextareaGhostProps;
uiProps: TextareaUIProps;
};
62 changes: 62 additions & 0 deletions src/textarea/TextareaSpinner.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import {
createComponent,
createElement,
createHook,
} from "ariakit-utils/system";
import { As, Props } from "ariakit-utils/types";

import { BoxOptions, useBox } from "../box";
import { useTheme } from "../theme";
import { cx } from "../utils";

import { TextareaBaseProps } from "./TextareaBase";
import { TextareaUIProps } from "./TextareaProps";

export const useTextareaSpinner = createHook<TextareaSpinnerOptions>(
({
size,
variant,
autoSize,
resize,
rowsMax,
rowsMin,
invalid,
loading,
disabled,
icon,
spinner,
autoSizeOnChange,
inputStyles,
inputRef,
ghostRef,
...props
}) => {
const theme = useTheme("textarea");
const className = cx(
theme.spinner.base,
autoSize || resize === "none" ? theme.spinner.autoSize : "",
props.className,
);

props = { ...props, className };
props = useBox(props);

return props;
},
);

export const TextareaSpinner = createComponent<TextareaSpinnerOptions>(
props => {
const htmlProps = useTextareaSpinner(props);

return createElement("div", htmlProps);
},
);

export type TextareaSpinnerOptions<T extends As = "div"> = BoxOptions<T> &
Partial<TextareaUIProps> &
Pick<TextareaBaseProps, "disabled"> & {};

export type TextareaSpinnerProps<T extends As = "div"> = Props<
TextareaSpinnerOptions<T>
>;
55 changes: 40 additions & 15 deletions src/textarea/TextareaUIState.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import React from "react";

import { SlotIcon } from "../icons";
import { RenderProp } from "../utils";

Expand All @@ -12,30 +14,53 @@ export function useTextareaUIState(
size = "md",
variant = "outline",
autoSize = false,
resize = autoSize ? "none" : "horizontal",
resize,
rowsMax = Infinity,
rowsMin = 1,
invalid = false,
loading = false,
icon = invalid ? <SlotIcon /> : null,
icon,
spinner = DefaultTextareaSpinner,
} = props;

const _resize = autoSize ? "none" : resize ?? "horizontal";
const _icon = React.useMemo(
() => (invalid ? <SlotIcon /> : icon),
[icon, invalid],
);

const autoSizeState = useAutoSize(props);

return {
size,
variant,
autoSize,
resize,
rowsMax,
rowsMin,
invalid,
loading,
icon,
spinner,
...autoSizeState,
};
const uiState = React.useMemo(
() => ({
size,
variant,
autoSize,
resize: _resize,
rowsMax,
rowsMin,
invalid,
loading,
icon: _icon,
spinner,
...autoSizeState,
}),
[
autoSize,
autoSizeState,
_icon,
invalid,
loading,
_resize,
rowsMax,
rowsMin,
size,
spinner,
variant,
],
);

return uiState;
}

export type TextareaUIState = {
Expand Down
10 changes: 4 additions & 6 deletions src/textarea/__utils.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,15 @@
import { Spinner } from "../spinner";
import { useTheme } from "../theme";
import { tcm } from "../utils";

import { TextareaUIProps } from "./TextareaProps";

export const DefaultTextareaSpinner = (props: TextareaUIProps) => {
const { autoSize, size } = props;
const theme = useTheme("textarea");
const { size } = props;

return (
<Spinner
className={tcm(theme.icon.base, autoSize ? theme.icon.autoSize : "")}
size={size !== "xl" ? "xs" : "md"}
track="visible"
themeColor="current"
size={size === "xl" ? "md" : "xs"}
/>
);
};
6 changes: 5 additions & 1 deletion src/theme/defaultTheme/textarea.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,15 @@ export const textarea = {
none: "resize-none",
},
},
ghost: "invisible absolute overflow-hidden h-0 top-0 left-0 transform-gpu",
spinner: {
base: "flex items-center justify-center absolute bottom-2.5 right-1 bg-transparent pointer-events-none",
autoSize: "bottom-1",
},
icon: {
base: "flex items-center justify-center absolute bottom-2.5 right-1 bg-transparent pointer-events-none",
autoSize: "bottom-1",
},
ghost: "invisible absolute overflow-hidden h-0 top-0 left-0 transform-gpu",
size: {
sm: {
base: "text-paragraph-cxs-cxs font-normal rounded-lg px-2 py-[5.5px]",
Expand Down

0 comments on commit 57e2230

Please sign in to comment.