Skip to content

Commit

Permalink
feat: create form builderV2 (#70)
Browse files Browse the repository at this point in the history
* feat: create form-builder-v2

* feat: add prefix-suffix label

* feat: update condition rendering label

* feat: add error helper form

* chore: bump version

* feat: add none error animation field

* feat: implement flexibility react-hook-form ref
  • Loading branch information
rianmandala authored Nov 18, 2024
1 parent 98b647d commit d8b10ee
Show file tree
Hide file tree
Showing 24 changed files with 605 additions and 158 deletions.
2 changes: 1 addition & 1 deletion lerna.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"packages": ["packages/*"],
"npmClient": "yarn",
"useWorkspaces": true,
"version": "0.10.2",
"version": "0.11.0",
"command": {
"version": {
"message": "chore(release): publish %s"
Expand Down
2 changes: 1 addition & 1 deletion packages/apsara-icons/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@goto-company/icons",
"version": "0.10.2",
"version": "0.11.0",
"description": "Apsara icons",
"scripts": {
"build": "node scripts/build.js",
Expand Down
9 changes: 6 additions & 3 deletions packages/apsara-ui/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@goto-company/apsara",
"version": "0.10.2",
"version": "0.11.0",
"description": "A list of base components for apsara",
"author": "Praveen Yadav <[email protected]>",
"license": "Apache-2.0",
Expand Down Expand Up @@ -32,7 +32,8 @@
"homepage": "https://goto.github.io/apsara/",
"peerDependencies": {
"react": "^16.13.1",
"react-dom": "^16.13.1"
"react-dom": "^16.13.1",
"react-hook-form": "7.43.1"
},
"dependencies": {
"@ant-design/icons": "^4.2.2",
Expand Down Expand Up @@ -75,7 +76,9 @@
"react-window": "^1.8.5",
"react-window-infinite-loader": "^1.0.5",
"styled-components": "^5.2.0",
"styled-system": "^5.1.5"
"styled-system": "^5.1.5",
"@hookform/error-message": "2.0.1",
"react-hook-form": "7.43.1"
},
"devDependencies": {
"@rollup/plugin-json": "^4.1.0",
Expand Down
9 changes: 6 additions & 3 deletions packages/apsara-ui/src/Checkbox/Checkbox.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { CheckIcon } from "@radix-ui/react-icons";
import React, { useEffect, useState } from "react";
import { CheckboxWrapper, CheckboxGroupWrapper, StyledCheckbox, StyledIndicator } from "./Checkbox.styles";
import { generateRandomId } from "../helper";
import transformCheckedValue from "../helper/transform-checked-value";

type CheckboxProps = {
defaultChecked?: boolean;
Expand Down Expand Up @@ -29,6 +30,7 @@ type CheckboxGroupProps = {
id?: string;
};

const prefixCls = "apsara-checkbox";
const Checkbox = ({
defaultChecked = false,
checked,
Expand All @@ -48,11 +50,11 @@ const Checkbox = ({
}, [checked]);

return (
<CheckboxWrapper className="apsara-checkbox-wrapper">
<CheckboxWrapper className={`${prefixCls}-wrapper`}>
<StyledCheckbox
defaultChecked={defaultChecked}
id={id}
checked={isChecked}
checked={isChecked || transformCheckedValue(value)}
onCheckedChange={
onChange ||
function (checked) {
Expand All @@ -64,6 +66,7 @@ const Checkbox = ({
name={name}
value={value}
style={style}
className={prefixCls}
>
<StyledIndicator>
<CheckIcon style={{ width: "13px", height: "13px" }} />
Expand Down Expand Up @@ -95,7 +98,7 @@ const CheckboxGroup = ({
};

return (
<CheckboxGroupWrapper orientation={orientation} className="apsara-checkbox-group">
<CheckboxGroupWrapper orientation={orientation} className={`${prefixCls}-group`}>
{options &&
options.map((option, index) => (
<div className="checkbox_label_wrapper" key={option.value}>
Expand Down
53 changes: 28 additions & 25 deletions packages/apsara-ui/src/Combobox/Combobox.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React from "react";
import { SelectProps, OptGroup, Option } from "rc-select";
import React, { forwardRef } from "react";
import Select, { SelectProps, OptGroup, Option } from "rc-select";
import { NotFoundContent, StyledMultiSelect } from "./Combobox.styles";
import { useState } from "react";
import Icon from "../Icon";
Expand All @@ -20,22 +20,23 @@ const loadingContent = (
</NotFoundContent>
);

const Combobox = ({
options,
mode,
value,
onChange,
onSearch,
onSelect,
onDeselect,
allowClear = true,
showSearch = true,
showArrow = true,
filterOption = true,
placeholder,
optionFilterProp,
...props
}: SelectProps) => {
const Combobox = forwardRef<Select<unknown>, SelectProps>((props, ref) => {
const {
options,
mode,
value,
onChange,
onSearch,
onSelect,
onDeselect,
allowClear = true,
showSearch = true,
showArrow = true,
filterOption = true,
placeholder,
optionFilterProp,
...restProps
} = props;
const [showInputIcon, setShowInputIcon] = useState(true);
const [isValue, setIsValue] = useState(false);
const [inputIcon, setInputIcon] = useState(ArrowIcon);
Expand Down Expand Up @@ -73,8 +74,8 @@ const Combobox = ({

return (
<StyledMultiSelect
notFoundContent={props.loading ? loadingContent : notFoundContent}
{...props}
notFoundContent={restProps.loading ? loadingContent : notFoundContent}
{...restProps}
showInputIcon={showInputIcon || !allowClear}
showSearch={showSearch}
mode={mode}
Expand All @@ -93,13 +94,15 @@ const Combobox = ({
filterOption={filterOption}
optionFilterProp={optionFilterProp || "value"}
animation="slide"
ref={ref}
>
{props.children}
{restProps.children}
</StyledMultiSelect>
);
};
});

Combobox.Option = Option;
Combobox.OptGroup = OptGroup;
Combobox.displayName = "Combobox";

export default Combobox;
const CompoundCombobox = Object.assign(Combobox, { Option, OptGroup });

export default CompoundCombobox;
26 changes: 15 additions & 11 deletions packages/apsara-ui/src/DatePicker/DatePicker.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useRef } from "react";
import React, { forwardRef, useRef } from "react";
import Picker from "rc-picker";
import enUS from "rc-picker/lib/locale/en_US";
import { CalendarOutlined, ClockCircleOutlined, CloseCircleFilled } from "@ant-design/icons/";
Expand All @@ -22,14 +22,15 @@ type DatePickerProps = {
};

const prefixCls = "apsara-picker";
const DatePicker = ({
className,
picker = "date",
placeholder = picker == "date" ? "Select date" : "Select time",
generateConfig = momentGenerateConfig,
width = "100%",
...props
}: DatePickerProps) => {
const DatePicker = forwardRef<null, DatePickerProps>((props, ref) => {
const {
className,
picker = "date",
placeholder = picker == "date" ? "Select date" : "Select time",
generateConfig = momentGenerateConfig,
width = "100%",
...restProps
} = props;
const datePickerDropdownWrapperRef = useRef<HTMLDivElement>(null);

return (
Expand All @@ -40,7 +41,7 @@ const DatePicker = ({
suffixIcon={picker === "time" ? <ClockCircleOutlined /> : <CalendarOutlined />}
clearIcon={<CloseCircleFilled />}
allowClear
{...props}
{...restProps}
className={className}
prefixCls={prefixCls}
locale={enUS}
Expand All @@ -52,11 +53,14 @@ const DatePicker = ({
nextIcon={<span className={`${prefixCls}-next-icon`} />}
superPrevIcon={<span className={`${prefixCls}-super-prev-icon`} />}
superNextIcon={<span className={`${prefixCls}-super-next-icon`} />}
ref={ref}
/>
</DatePickerWrapper>
<PickerDropdownWrapper ref={datePickerDropdownWrapperRef} />
</>
);
};
});

DatePicker.displayName = "DatePicker";

export default DatePicker;
1 change: 1 addition & 0 deletions packages/apsara-ui/src/Input/Input.styles.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,7 @@ const StyledWrapper = styled("div")<{
`;

export const TextAreaWrapper = styled("div")<{ size?: "small" | "middle" | "large" }>`
display: flex;
.input_textarea_main {
width: 100%;
border: 1px solid ${({ theme }) => theme?.input?.border};
Expand Down
68 changes: 40 additions & 28 deletions packages/apsara-ui/src/Input/Input.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import React, { useState } from "react";
import React, { forwardRef, useImperativeHandle, useRef } from "react";
import StyledWrapper, { TextAreaWrapper } from "./Input.styles";
import { PREFIX_CLS } from "./constants";

export type InputProps = {
size?: "small" | "middle" | "large";
Expand All @@ -12,44 +13,51 @@ type TextAreaProps = {
size?: "small" | "middle" | "large";
} & Omit<React.HTMLProps<HTMLTextAreaElement>, "size">;

const Input = ({
suffix,
prefix,
placeholder = "",
allowClear = false,
size = "middle",
onChange,
type,
value,
...props
}: InputProps) => {
const [ref, setRef] = useState<HTMLInputElement | null>(null);
const renderClearButton = value && allowClear && !props.disabled;
const Input = forwardRef<HTMLInputElement, InputProps>((props, ref) => {
const {
suffix,
prefix,
placeholder = "",
allowClear = false,
size = "middle",
onChange,
type,
value,
...restProps
} = props;
const localRef = useRef<HTMLInputElement>(null);
const renderClearButton = value && allowClear && !restProps.disabled;

// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
useImperativeHandle(ref, () => localRef.current!, []);

const onValueChange = (e: any) => {
onChange && onChange(e);
};

return (
<StyledWrapper size={size} disabled={props.disabled} style={props.style} className={props.className}>
<StyledWrapper
size={size}
disabled={restProps.disabled}
style={restProps.style}
className={`${PREFIX_CLS} ${restProps.className || ""}`}
>
{prefix != "" && prefix != null && <span className="input_suffix_prefix">{prefix}</span>}
<div className="input_close_icon_wrapper">
<input
ref={(input) => {
setRef(input);
}}
ref={localRef}
onChange={onValueChange}
value={value}
type={type ? type : "text"}
placeholder={placeholder}
{...props}
{...restProps}
className="input_main"
/>
{renderClearButton && (
<span
onClick={() => {
onValueChange({ target: { value: "" } });
ref?.focus();
localRef.current?.focus();
}}
className="input_close_icon"
>
Expand All @@ -58,21 +66,25 @@ const Input = ({
)}
</div>
{suffix != "" && suffix != null && <span className="input_suffix_prefix input_suffix">{suffix}</span>}
{props.children}
{restProps.children}
</StyledWrapper>
);
};
});

const TextArea = ({ size = "middle", ...props }: TextAreaProps) => {
const TextArea = forwardRef<HTMLTextAreaElement, TextAreaProps>((props, ref) => {
const { size = "middle", ...restProps } = props;
return (
<TextAreaWrapper size={size}>
<textarea {...props} className="input_textarea_main">
{props.children}
<textarea {...restProps} ref={ref} className={`${PREFIX_CLS}-text-area input_textarea_main`}>
{restProps.children}
</textarea>
</TextAreaWrapper>
);
};
});

TextArea.displayName = "TextArea";
Input.displayName = "Input";

Input.TextArea = TextArea;
const CompoundInput = Object.assign(Input, { TextArea });

export default Input;
export default CompoundInput;
1 change: 1 addition & 0 deletions packages/apsara-ui/src/Input/constants/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const PREFIX_CLS = "apsara-input";
Loading

0 comments on commit d8b10ee

Please sign in to comment.