diff --git a/ui-kit/src/components/Input/index.tsx b/ui-kit/src/components/Input/index.tsx index 3097f8d1..22716bb5 100644 --- a/ui-kit/src/components/Input/index.tsx +++ b/ui-kit/src/components/Input/index.tsx @@ -9,6 +9,7 @@ type Props = CombineElementProps< { label?: ReactNode; type?: TextInputType; + left?: ReactNode; right?: ReactNode; hasError?: boolean; description?: string; @@ -21,6 +22,7 @@ const Input = forwardRef(function Input( className, disabled, type = 'text', + left, right, hasError, description, @@ -33,6 +35,7 @@ const Input = forwardRef(function Input( const [isFocused, setFocus] = useState(false); const hasLabel = label != null; const hasDescription = description != null; + const hasLeftArea = left != null; const hasRightArea = right != null; const labelElement = useMemo(() => { @@ -64,9 +67,11 @@ const Input = forwardRef(function Input( {labelElement}
+ {left != null ? {left} : null} void; + children: ReactText; + } +>; + +const Tag = ({ + type = 'default', + className, + children: label, + onClick, + onDelete, + ...props +}: Props) => { + const isClickable = onClick != null || onDelete != null; + return ( +
+ + {isValidElement(label) ? label : {label}} + + {onDelete != null ? ( + onDelete?.(label)}> + + + ) : null} +
+ ); +}; + +export default Tag; diff --git a/ui-kit/src/index.ts b/ui-kit/src/index.ts index 74b7ddcd..4acd8b92 100644 --- a/ui-kit/src/index.ts +++ b/ui-kit/src/index.ts @@ -19,10 +19,11 @@ export { CardFooter, } from './components/Card'; export { default as Snackbar } from './components/Snackbar'; -export { default as List, ListItem } from './components/List'; +export { default as List, ListItem, ListItemImage } from './components/List'; export { default as Input } from './components/Input'; export { default as ProgressBar } from './components/ProgressBar'; export { default as Accordion } from './components/Accordion'; +export { default as Tag } from './components/Tag'; export { default as Modal, ModalHeader, ModalContent, ModalFooter } from './components/Modal'; export { Portal } from './contexts/Portal'; export { useToast } from './contexts/Toast'; diff --git a/ui-kit/src/sass/components/_Input.scss b/ui-kit/src/sass/components/_Input.scss index 2e9e2598..bbf66222 100644 --- a/ui-kit/src/sass/components/_Input.scss +++ b/ui-kit/src/sass/components/_Input.scss @@ -66,6 +66,7 @@ $description-position: 30px; color: get-color('gray70'); } } + &__left, &__right { display: inline-flex; } diff --git a/ui-kit/src/sass/components/_Tag.scss b/ui-kit/src/sass/components/_Tag.scss new file mode 100644 index 00000000..faedc77e --- /dev/null +++ b/ui-kit/src/sass/components/_Tag.scss @@ -0,0 +1,64 @@ +@mixin tagColor($color, $hover-color) { + background-color: $color; + &:hover { + background-color: $hover-color; + } +} + +.lubycon-tag { + display: flex; + align-items: center; + justify-content: space-between; + padding: 4px 0 4px 12px; + border-radius: 4px; + transition: background-color 0.1s ease-in-out; + + &--type { + &-default { + background-color: get-color('gray30'); + } + &-positive { + background-color: get-color('green40'); + } + &-informative { + background-color: get-color('blue40'); + } + &-notice { + background-color: get-color('yellow40'); + } + &-negative { + background-color: get-color('red40'); + } + } + + &--clickable { + cursor: pointer; + &.lubycon-tag--type-default:hover { + background-color: get-color('gray40'); + } + &.lubycon-tag--type-positive:hover { + background-color: #c9f5d8; + } + &.lubycon-tag--type-informative:hover { + background-color: #d0e1fe; + } + &.lubycon-tag--type-notice:hover { + background-color: #fdf1b6; + } + &.lubycon-tag--type-negative:hover { + background-color: #fad2d4; + } + } + + &__label { + margin-right: 12px; + color: get-color('gray90'); + user-select: none; + } + + &__delete-button { + cursor: pointer; + display: flex; + margin-right: 8px; + } +} diff --git a/ui-kit/src/sass/components/_index.scss b/ui-kit/src/sass/components/_index.scss index d154b1b3..dedba876 100644 --- a/ui-kit/src/sass/components/_index.scss +++ b/ui-kit/src/sass/components/_index.scss @@ -18,4 +18,5 @@ @import './List'; @import './Input'; @import './ProgressBar'; +@import './Tag'; @import './Modal'; diff --git a/ui-kit/src/stories/Input.stories.tsx b/ui-kit/src/stories/Input.stories.tsx index 441bdfac..0896ee10 100644 --- a/ui-kit/src/stories/Input.stories.tsx +++ b/ui-kit/src/stories/Input.stories.tsx @@ -2,7 +2,7 @@ import React, { useState } from 'react'; import { Input, Text, colors } from 'src'; import { Meta } from '@storybook/react/types-6-0'; import Icon from 'src/components/Icon'; -import { checkmarkCircle } from 'ionicons/icons'; +import { checkmarkCircle, closeCircle, musicalNote } from 'ionicons/icons'; import { TextInputType } from 'components/Input'; export default { @@ -75,3 +75,17 @@ export const Types = () => {
); }; + +export const LeftAndRight = () => { + return ( +
+ } /> + } /> + } + right={} + /> +
+ ); +}; diff --git a/ui-kit/src/stories/List.stories.tsx b/ui-kit/src/stories/List.stories.tsx index 6cfba1d9..57bed02d 100644 --- a/ui-kit/src/stories/List.stories.tsx +++ b/ui-kit/src/stories/List.stories.tsx @@ -1,10 +1,8 @@ import React from 'react'; -import { Button, List, ListItem } from 'src'; +import { Button, List, ListItem, ListItemImage, colors } from 'src'; import { Meta } from '@storybook/react/types-6-0'; -import { ListItemImage } from 'src/components/List'; import Icon from 'src/components/Icon'; import { chevronForward } from 'ionicons/icons'; -import { colors } from 'src/constants/colors'; export default { title: 'Lubycon UI Kit/List', diff --git a/ui-kit/src/stories/Tag.stories.tsx b/ui-kit/src/stories/Tag.stories.tsx new file mode 100644 index 00000000..c628f778 --- /dev/null +++ b/ui-kit/src/stories/Tag.stories.tsx @@ -0,0 +1,60 @@ +import React from 'react'; +import { Tag } from 'src'; +import { Meta } from '@storybook/react/types-6-0'; +import { SemanticColor } from 'src/constants/colors'; + +export default { + title: 'Lubycon UI Kit/Tag', +} as Meta; + +const samples: Array<{ label: string; type?: SemanticColor }> = [ + { + label: 'chore', + type: undefined, + }, + { + label: '디자인 챕터', + type: 'positive', + }, + { + label: '프론트엔드 챕터', + type: 'informative', + }, + { + label: 'MVP', + type: 'negative', + }, + { + label: 'feature', + type: 'notice', + }, +]; + +export const Default = () => { + return ( +
+ {samples.map(({ label, type }, index) => ( + + {label} + + ))} +
+ ); +}; + +export const DeleteButton = () => { + return ( +
+ {samples.map(({ label, type }, index) => ( + console.log(`${label} is deleted`)} + > + {label} + + ))} +
+ ); +};