From 1a0ee18ee3d92e77507baa5b9d58e15a287e522a Mon Sep 17 00:00:00 2001 From: Jaeho Lee Date: Mon, 12 Dec 2022 14:33:23 +0900 Subject: [PATCH] =?UTF-8?q?TextField=20=EC=98=B5=EC=85=98=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=20=EB=B0=8F=20TextTab=20=EB=B0=94=EC=9D=B8=EB=94=A9?= =?UTF-8?q?=20=EC=B6=94=EA=B0=80=20(#89)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: add TextField options - showHintOnFocusOnly, hideClearButton * feat: TextField options ReScript bindings * feat: TextTab export / add ReScript binding * release --- .changeset/three-coats-float.md | 7 ++ .../__tests__/Formula_test.res | 41 +++++- packages/components-rescript/src/Formula.res | 1 + .../src/Formula__TextField.res | 10 +- .../src/Formula__TextTab.res | 37 ++++++ .../components/src/Tab/TextTab/TextTab.tsx | 119 ++++++++++-------- packages/components/src/Tab/index.tsx | 1 - .../src/TextField/TextField.stories.tsx | 18 ++- .../components/src/TextField/TextField.tsx | 45 ++++--- .../components/src/TextField/styles.css.ts | 9 +- packages/components/src/index.ts | 3 + 11 files changed, 212 insertions(+), 79 deletions(-) create mode 100644 .changeset/three-coats-float.md create mode 100644 packages/components-rescript/src/Formula__TextTab.res delete mode 100644 packages/components/src/Tab/index.tsx diff --git a/.changeset/three-coats-float.md b/.changeset/three-coats-float.md new file mode 100644 index 00000000..8b36a8c2 --- /dev/null +++ b/.changeset/three-coats-float.md @@ -0,0 +1,7 @@ +--- +"@greenlabs/formula-components": patch +"@greenlabs/rescript-formula-components": patch +--- + +- feat: add TextField options (showHintOnFocusOnly, hideClearButton) +- feat: TextField options ReScript bindings diff --git a/packages/components-rescript/__tests__/Formula_test.res b/packages/components-rescript/__tests__/Formula_test.res index d77badc3..503c5d0a 100644 --- a/packages/components-rescript/__tests__/Formula_test.res +++ b/packages/components-rescript/__tests__/Formula_test.res @@ -1,3 +1,5 @@ +open Formula + module TestTextContainer = { @react.component let make = (~className=?, ~children=?) => { @@ -6,7 +8,6 @@ module TestTextContainer = { } let testText = () => { - open Formula <> { } let testIcon = () => { - open Formula <> @@ -43,7 +43,6 @@ let testIcon = () => { } let testDivider = () => { - open Formula <> @@ -51,7 +50,6 @@ let testDivider = () => { } let testButton = () => { - open Formula <> @@ -69,7 +67,6 @@ let testButton = () => { } let testTextField = () => { - open Formula <> @@ -95,9 +92,43 @@ let testTextField = () => { }} /> + } +let textTextTab = () => { + ()} + contents={<> + {`this is a`->React.string} + {`this is b`->React.string} + {`this is c`->React.string} + }> + + + + + + +} + let testCommon = () => {
} diff --git a/packages/components-rescript/src/Formula.res b/packages/components-rescript/src/Formula.res index 05b794b9..eb70a7da 100644 --- a/packages/components-rescript/src/Formula.res +++ b/packages/components-rescript/src/Formula.res @@ -5,3 +5,4 @@ module TextField = Formula__TextField module Icon = Formula__Icon module Divider = Formula__Divider module Button = Formula__Button +module TextTab = Formula__TextTab diff --git a/packages/components-rescript/src/Formula__TextField.res b/packages/components-rescript/src/Formula__TextField.res index b26da7a6..979620ec 100644 --- a/packages/components-rescript/src/Formula__TextField.res +++ b/packages/components-rescript/src/Formula__TextField.res @@ -12,6 +12,11 @@ type textFieldComponentProps<'a> = { disabled?: bool, } +type options = { + showHintOnFocusOnly?: bool, + hideClearButton?: bool, +} + @module("@greenlabs/formula-components") @react.component external make: ( ~props: {..}=?, @@ -24,9 +29,9 @@ external make: ( ~_type: [#text | #password]=?, ~placeholder: string=?, ~prefix: React.element=?, - ~prefixIcon: React.componentLike<{..}, React.element>=?, + ~prefixIcon: React.componentLike<{..}, React.element>=?, // FIXME: Icon component type ~suffix: React.element=?, - ~suffixIcon: React.componentLike<{..}, React.element>=?, + ~suffixIcon: React.componentLike<{..}, React.element>=?, // FIXME: Icon component type ~titleText: string=?, ~hintText: string=?, ~state: [#normal | #error]=?, @@ -35,4 +40,5 @@ external make: ( ~onChange: ReactEvent.Form.t => unit=?, ~onFocus: ReactEvent.Focus.t => unit=?, ~ref: ReactDOM.Ref.t=?, + ~options: options=?, ) => React.element = "TextField" diff --git a/packages/components-rescript/src/Formula__TextTab.res b/packages/components-rescript/src/Formula__TextTab.res new file mode 100644 index 00000000..fcdfff9a --- /dev/null +++ b/packages/components-rescript/src/Formula__TextTab.res @@ -0,0 +1,37 @@ +module List = { + @module("@greenlabs/formula-components") @react.component + external make: ( + ~props: {..}=?, + ~rootProps: {..}=?, // TODO: RadixUI props + ~contents: React.element=?, + ~fullWidth: bool=?, + ~onValueChange: string => unit=?, + ~children: React.element, + ~ref: ReactDOM.Ref.t=?, + ) => React.element = "TextTab" +} + +module Trigger = { + type badgeType = { + type_: [#count | #simple | #countSimple], + value?: int, + } + + @module("@greenlabs/formula-components") @react.component + external make: ( + ~icon: React.componentLike<{..}, React.element>=?, // FIXME: Icon component type + ~title: string=?, + ~badge: badgeType=?, + ~value: string, + ~children: React.element=?, + ) => React.element = "TextTab" +} + +module Content = { + @module("@greenlabs/formula-components") @react.component + external make: ( + ~value: string, + ~children: React.element=?, + ~props: ReactDOM.domProps=?, + ) => React.element = "TextTab" +} diff --git a/packages/components/src/Tab/TextTab/TextTab.tsx b/packages/components/src/Tab/TextTab/TextTab.tsx index 3c3dd1b1..a6221fee 100644 --- a/packages/components/src/Tab/TextTab/TextTab.tsx +++ b/packages/components/src/Tab/TextTab/TextTab.tsx @@ -1,5 +1,11 @@ import type { PropsWithChildren, ReactNode } from "react" -import { useLayoutEffect, useRef, useState } from "react" +import { + forwardRef, + useLayoutEffect, + useRef, + useState, + useImperativeHandle, +} from "react" import type { TabsProps } from "@radix-ui/react-tabs" import { Root, TabsList } from "@radix-ui/react-tabs" import { @@ -25,61 +31,70 @@ interface ListProps { onValueChange?: (value: string) => void } // todo: on resize -export const List = ({ - children, - contents = null, - onValueChange, - fullWidth, - rootProps, - ...props -}: PropsWithChildren) => { - const ref = useRef(null) - const [state, setState] = useState({ left: 0, width: 0 }) +export const List = forwardRef>( + ( + { children, contents = null, onValueChange, fullWidth, rootProps }, + forwardedRef + ) => { + const ref = useRef(null) + const [state, setState] = useState({ left: 0, width: 0 }) + + useImperativeHandle(forwardedRef, () => ref.current as HTMLDivElement) - useLayoutEffect(() => { - const tabEl = ref.current?.querySelector("button") - if (tabEl) { - setState(extractIndicatorState(tabEl)) + useLayoutEffect(() => { + const tabEl = ref.current?.querySelector("button") + if (tabEl) { + setState(extractIndicatorState(tabEl)) + } + }, []) + + const onValueChangeWrapped = (value: string) => { + onValueChange?.(value) + // indicator animation + const listEl = ref.current + const activeTabEl = listEl?.querySelector( + '[data-state="active"]' + ) + if (activeTabEl) { + setState(extractIndicatorState(activeTabEl)) + } } - }, []) - const onValueChangeWrapped = (value: string) => { - onValueChange?.(value) - // indicator animation - const listEl = ref.current - const activeTabEl = listEl?.querySelector( - '[data-state="active"]' + return ( + +
+ + {children} + +
+
+ {contents} + ) - if (activeTabEl) { - setState(extractIndicatorState(activeTabEl)) - } } - - return ( - -
- - {children} - -
-
- {contents} - - ) -} +) export { Trigger } from "./Trigger" -export { Content } from "@radix-ui/react-tabs" +import { Content as RadixTabsContent } from "@radix-ui/react-tabs" + +export const Content = forwardRef< + HTMLDivElement, + { + value: string + children?: React.ReactNode + props?: React.RefAttributes + } +>(({ value, props }) => ) diff --git a/packages/components/src/Tab/index.tsx b/packages/components/src/Tab/index.tsx deleted file mode 100644 index a462aea4..00000000 --- a/packages/components/src/Tab/index.tsx +++ /dev/null @@ -1 +0,0 @@ -export * from "./TextTab/TextTab" diff --git a/packages/components/src/TextField/TextField.stories.tsx b/packages/components/src/TextField/TextField.stories.tsx index c416be18..c7e6345a 100644 --- a/packages/components/src/TextField/TextField.stories.tsx +++ b/packages/components/src/TextField/TextField.stories.tsx @@ -72,6 +72,7 @@ const commonDisabled = createDisabledArgs([ "suffixIcon", "onChange", "onFocus", + "options", ]) const iconControlMapping = Object.entries(IconComponents).reduce( @@ -156,7 +157,9 @@ Line.args = { } Line.argTypes = BoxOutline.argTypes -export const Textarea_and_Ref: ComponentStory = (args) => { +export const Textarea_and_Ref_Etc: ComponentStory = ( + args +) => { const ref = React.useRef(null) React.useLayoutEffect(() => { @@ -205,14 +208,23 @@ export const Textarea_and_Ref: ComponentStory = (args) => { titleText="using `react-textarea-autosize` as `inputContainer`" inputContainer={inputContainer} /> +
+ )} /> ) } -Textarea_and_Ref.args = Overview.args -Textarea_and_Ref.argTypes = { +Textarea_and_Ref_Etc.args = Overview.args +Textarea_and_Ref_Etc.argTypes = { ...commonDisabled, ...controls, } diff --git a/packages/components/src/TextField/TextField.tsx b/packages/components/src/TextField/TextField.tsx index 1b478f9e..4fb0e20c 100644 --- a/packages/components/src/TextField/TextField.tsx +++ b/packages/components/src/TextField/TextField.tsx @@ -4,6 +4,7 @@ import { useRef } from "react" import { TextVariant } from "../Text/Text" import type { variantKeyType as textVariantKey } from "../Text/Text" import type { textFieldSizeVariants } from "./styles.css" +import { assignInlineVars } from "@vanilla-extract/dynamic" import { textFieldVariants, inputStyle, @@ -15,6 +16,7 @@ import { hintStyle, clearButtonStyle, componentStyle, + vars, } from "./styles.css" import type { IconProps } from "../Icon" import { DeleteFill } from "../Icon" @@ -58,6 +60,10 @@ type TextFieldProps = PropsWithChildren< titleText?: string // title text to be shown upper side hintText?: string // hint text to be shown below state?: "normal" | "error" // visual states (focused, readonly or disabled is separated as prop/attr) + options?: { + showHintOnFocusOnly?: boolean + hideClearButton?: boolean + } } > @@ -85,6 +91,10 @@ export const TextField = React.forwardRef( state, onChange, onFocus, + options = { + hideClearButton: false, + showHintOnFocusOnly: false, + }, }, forwardedRef ) => { @@ -134,6 +144,11 @@ export const TextField = React.forwardRef( } )}`} htmlFor={id ?? innerId} + style={ + options.showHintOnFocusOnly + ? assignInlineVars({ [vars.optionalHintColor]: "transparent" }) + : undefined + } > {titleText ? ( @@ -165,20 +180,22 @@ export const TextField = React.forwardRef( disabled={disabled} {...props} /> - { - const inputEl = inputRef.current - if (inputEl) { - inputEl.value = "" - } - }} - > - - + {!options.hideClearButton && ( + { + const inputEl = inputRef.current + if (inputEl) { + inputEl.value = "" + } + }} + > + + + )} {suffix ? ( {suffix} ) : SuffixIcon ? ( diff --git a/packages/components/src/TextField/styles.css.ts b/packages/components/src/TextField/styles.css.ts index a56f86a6..c1a7db7f 100644 --- a/packages/components/src/TextField/styles.css.ts +++ b/packages/components/src/TextField/styles.css.ts @@ -20,6 +20,7 @@ export const vars = { iconOffset: createVar(), hintOffset: createVar(), titleOffset: createVar(), + optionalHintColor: createVar(), } const consts = { @@ -109,6 +110,7 @@ export const componentStyle = styleVariants({ ], }) +const hintStateColor = fallbackVar(vars.stateColor, theme.colors["green-70"]) export const hintStyle = style({ paddingTop: fallbackVar(vars.hintOffset, consts.hintOffset), color: fallbackVar( @@ -124,8 +126,11 @@ export const hintStyle = style({ fontSize: caption.xs.regular["font-size"].value, }, // when focused (or if have errornous state) apply different text color - ":focus-within ~ &": { - color: fallbackVar(vars.stateColor, theme.colors["green-70"]), + [`${containerStyle}:focus-within ~ &`]: { + color: hintStateColor, + }, + [`${containerStyle}:not(:focus-within) ~ &`]: { + color: fallbackVar(vars.optionalHintColor, hintStateColor), }, }, }) diff --git a/packages/components/src/index.ts b/packages/components/src/index.ts index 1f155ae3..c6edd2bb 100644 --- a/packages/components/src/index.ts +++ b/packages/components/src/index.ts @@ -5,3 +5,6 @@ export * from "./Icon" export * from "./Divider/Divider" export * from "./Button" export * from "./TextField/TextField" + +import * as TextTab from "./Tab/TextTab/TextTab" +export { TextTab }