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

LH-377/tab #18

Merged
merged 10 commits into from
Apr 25, 2023
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
"use client";

import { ClassNames, PolymorphicRef } from "@travelmakers-design-v2/styles";
import { forwardRef, useEffect } from "react";
import { View } from "../../View";
Expand Down
24 changes: 16 additions & 8 deletions packages/travelmakers-design-core/src/components/Input/Input.tsx
Original file line number Diff line number Diff line change
@@ -1,23 +1,24 @@
import { PolymorphicRef } from "@travelmakers-design-v2/styles";
import { forwardRef, useState } from "react";
import { forwardRef, useEffect, useState } from "react";
import { useId } from "../../../../travelmakers-design-hooks/src";
import { View } from "../View";
import useStyles from "./Input.style";
import { InputComponent, InputProps } from "./Input.type";

export interface Props extends React.HTMLAttributes<HTMLInputElement> {
name: string;
label?: string;
subfix?: string | number;
feedback?: string;
isError?: boolean;
}

let defaultId = 0;

export const Input: InputComponent & {
displayName?: string;
} = forwardRef(
<C extends React.ElementType = "input">(
{
name,
label,
subfix,
feedback,
Expand All @@ -33,11 +34,16 @@ export const Input: InputComponent & {
}: InputProps<C>,
ref: PolymorphicRef<C>
) => {
const id = useId(name);
const [inputValue, setInputValue] = useState(value ?? "");
const [isFocus, setIsFocus] = useState(false);
const { classes, cx } = useStyles({ subfix, isError });
const [id] = useState(() => String(defaultId++));
const elementId = `tm-input-${id}`;

useEffect(() => {
if (name) return;

console.error("The Input component requires a name prop to be used.");
}, [name]);

const onClickHandler = (e: React.MouseEvent<HTMLInputElement>) => {
setIsFocus(true);
Expand Down Expand Up @@ -68,7 +74,7 @@ export const Input: InputComponent & {
<View<React.ElementType>
component={"input"}
className={classes.input}
id={elementId}
id={id}
type={"text"}
ref={ref}
placeholder={placeholder}
Expand All @@ -77,6 +83,7 @@ export const Input: InputComponent & {
onBlur={onBlurHandler}
onChange={onChangeHandler}
value={inputValue}
name={name}
{...props}
/>
<div className={classes.subfix} aria-readonly={props.disabled}>
Expand All @@ -90,7 +97,7 @@ export const Input: InputComponent & {
<View<React.ElementType>
component={"input"}
className={cx(classes.input, classes.container)}
id={elementId}
id={id}
type={"text"}
ref={ref}
placeholder={placeholder}
Expand All @@ -99,6 +106,7 @@ export const Input: InputComponent & {
onBlur={onBlur}
onChange={onChangeHandler}
value={inputValue}
name={name}
aria-readonly={props.disabled}
{...props}
/>
Expand All @@ -108,7 +116,7 @@ export const Input: InputComponent & {
return (
<div className={cx(classes.root, className)}>
{label && (
<label className={classes.label} htmlFor={elementId}>
<label className={classes.label} htmlFor={id}>
{label}
</label>
)}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,17 @@ export default {
title: "@travelmakers-design-v2/core/General/Input",
component: Input,
argTypes: {
name: {
baegofda marked this conversation as resolved.
Show resolved Hide resolved
control: {
type: "text",
},
description: "input의 id 생성을 위한 필수 요소",
table: {
type: {
summary: "string",
},
},
},
label: {
control: {
type: "text",
Expand All @@ -20,7 +31,7 @@ export default {
control: {
type: "text",
},
description: "input 사용 시 인증 시간,결과 등을 나타냅니다.",
description: "input 사용 시 인증 시간, 결과 등을 나타냅니다.",
table: {
type: {
summary: ["string", "number"],
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { createStyles } from "@travelmakers-design-v2/styles";
// import { Props } from "./PrimaryTab";

/** style에서 사용할 Props Type을 선언합니다.
* ex)
* export default createStyles((theme, { type }: Pick<Props, "prop">) => {
* // writhing styles
* }
* */
baegofda marked this conversation as resolved.
Show resolved Hide resolved
export default createStyles((theme) => {
return {
root: { display: "flex", margin: 0, padding: 0 },
};
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { PolymorphicRef } from "@travelmakers-design-v2/styles";
import React, { forwardRef } from "react";
import { View } from "../../View";
import { PrimaryTabItem } from "../PrimaryTabItem";
import useStyles from "./PrimaryTab.style";
import { PrimaryTabComponent, PrimaryTabProps } from "./PrimaryTab.type";

export interface Props extends React.HTMLAttributes<HTMLUListElement> {
items: React.ReactNode[];
}

export const PrimaryTab: PrimaryTabComponent & {
displayName?: string;
Item?: typeof PrimaryTabItem;
} = forwardRef(
<C extends React.ElementType = "ul">(
{ items, className, ...props }: PrimaryTabProps<C>,
ref: PolymorphicRef<C>
) => {
const { classes, cx } = useStyles();

return (
<View<React.ElementType>
component={"ul"}
ref={ref}
className={cx(classes.root, className)}
{...props}
>
{items}
</View>
);
}
);

PrimaryTab.displayName = "PrimaryTab";
PrimaryTab.Item = PrimaryTabItem;
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import {
ClassNames,
PolymorphicComponentProps,
TmComponentProps,
} from "@travelmakers-design-v2/styles";
import { Props } from "./PrimaryTab";
import useStyles from "./PrimaryTab.style";

type PrimaryTabStylesNames = ClassNames<typeof useStyles>;

interface SharedPrimaryTabProps
extends Props,
TmComponentProps<PrimaryTabStylesNames> {}

export type PrimaryTabProps<C extends React.ElementType> =
PolymorphicComponentProps<C, SharedPrimaryTabProps>;

export type PrimaryTabComponent = <C extends React.ElementType = "ul">(
props: PrimaryTabProps<C>
) => React.ReactElement;
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export { PrimaryTab } from "./PrimaryTab";
export type { PrimaryTabProps } from "./PrimaryTab.type";
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { Meta } from "@storybook/react";
import { useState } from "react";
import { PrimaryTab } from "../PrimaryTab";

const data = ["서울", "제주", "경상", "수도권"];

export default {
title: "@travelmakers-design-v2/core/General/Tab/PrimaryTab/PrimaryTab",
component: PrimaryTab,
argTypes: {
items: {
control: { type: "array" },
description: "Tab에 표시될 리스트 입니다. PrimaryTab.Item를 사용합니다.",
table: {
type: {
summary: "React.ReactNode[]",
},
},
},
},
} as Meta;

export const Default = () => {
baegofda marked this conversation as resolved.
Show resolved Hide resolved
const [currentTab, setCurrentTab] = useState("서울");

const _items = data.map((item, idx) => (
<PrimaryTab.Item
key={idx}
label={item}
value={item}
onChange={(e) => {
const { value } = e.target as HTMLInputElement;

setCurrentTab(value);
}}
checked={item === currentTab}
/>
));

return <PrimaryTab items={_items} />;
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { createStyles } from "@travelmakers-design-v2/styles";

export default createStyles((theme) => {
const { spacing, typography, colors } = theme;

return {
item: {
display: "flex",
alignItems: "center",

".tm-tab__content": { ...typography.body1, color: colors.primary3 },
baegofda marked this conversation as resolved.
Show resolved Hide resolved
"&:last-child > .tm-tab__divider": { display: "none" },
"& > .tm-tab__divider": { margin: `0 ${spacing.spacing20}` },
},
label: { minWidth: "52px", textAlign: "center", cursor: "pointer" },
input: {
"&:checked + .tm-tab__content": {
...typography.subhead1,
color: colors.secondary1,
fontWeight: 700,
},
},
};
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import { PolymorphicRef } from "@travelmakers-design-v2/styles";
import { ChangeEvent, forwardRef } from "react";
import { useId } from "../../../../../travelmakers-design-hooks/src";
import { Divider } from "../../Divider";
import { View } from "../../View";
import useStyles from "./PrimaryTabItem.style";
import {
PrimaryTabItemComponent,
PrimaryTabItemProps,
} from "./PrimaryTabItem.type";

export interface Props extends React.HTMLAttributes<HTMLInputElement> {
name?: string;
label: string;
}

export const PrimaryTabItem: PrimaryTabItemComponent & {
displayName?: string;
} = forwardRef(
<C extends React.ElementType = "input">(
{
name = "primary-tab-item",
label,
className,
onChange,
...props
}: PrimaryTabItemProps<C>,
ref: PolymorphicRef<C>
) => {
const id = useId(name);
const { classes, cx } = useStyles();

const onChangeHandler = (e: ChangeEvent<HTMLInputElement>) => {
onChange?.(e);
};

return (
<li className={cx(classes.item, className)}>
<label htmlFor={id} className={classes.label}>
<View<React.ElementType>
component={"input"}
id={id}
ref={ref}
type="radio"
name={name}
className={cx(classes.input, "sr-only")}
onChange={onChangeHandler}
{...props}
/>
<div className={"tm-tab__content"}>{label}</div>
</label>
<Divider className={"tm-tab__divider"} type="vertical" height="20px" />
</li>
);
}
);

PrimaryTabItem.displayName = "PrimaryTabItem";
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import {
ClassNames,
PolymorphicComponentProps,
TmComponentProps,
} from "@travelmakers-design-v2/styles";
import { Props } from "./PrimaryTabItem";
import useStyles from "./PrimaryTabItem.style";

type PrimaryTabItemStylesNames = ClassNames<typeof useStyles>;

interface SharedPrimaryTabItemProps
extends Props,
TmComponentProps<PrimaryTabItemStylesNames> {}

export type PrimaryTabItemProps<C extends React.ElementType> =
PolymorphicComponentProps<C, SharedPrimaryTabItemProps>;

export type PrimaryTabItemComponent = <C extends React.ElementType = "input">(
props: PrimaryTabItemProps<C>
) => React.ReactElement;
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export { PrimaryTabItem } from "./PrimaryTabItem";
export type { PrimaryTabItemProps } from "./PrimaryTabItem.type";
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { Meta } from "@storybook/react";
import { PrimaryTabItem } from "../PrimaryTabItem";

export default {
title: "@travelmakers-design-v2/core/General/Tab/PrimaryTab/PrimaryTabItem",
component: PrimaryTabItem,
argTypes: {
name: {
control: {
type: "text",
},
defaultValue: "primary-tab-item",
description: "PrimaryTabItem들의 radio group을 지정합니다.",
table: {
type: {
summary: "string",
},
},
},
label: {
control: { type: "text" },
defaultValue: "label",
description: "PrimaryTabItem label",
table: {
type: {
summary: "string",
},
},
},
checked: {
control: { type: "boolean" },
defaultValue: false,
description: "PrimaryTabItem checked 여부",
table: {
type: {
summary: "boolean",
},
},
},
},
} as Meta;

export const Default = (props) => {
return <PrimaryTabItem {...props} />;
};
Loading