Skip to content
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@ export const LockedCharacterCard = styled(CardContainerBase)`
padding-bottom: 2.25rem; /* 의도적으로 토큰으로 정의되지 않은 값 사용 */
padding-left: ${getToken('padding.5')};
padding-right: ${getToken('padding.5')};
border: 1px dashed #d2d4d5; /* HACK: 토큰에 정의되어 있지 않아 임시로 하드코딩함 */
border: 1px dashed ${getToken('border.normal')};
opacity: 0.5;
`;

export const CharacterImage = styled.img`
Expand Down
4 changes: 2 additions & 2 deletions src/features/character-management/ui/CharacterItem/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ import * as S from './index.styles';
function LockedCharacterCard() {
return (
<S.LockedCharacterCard aria-label="잠긴 캐릭터">
{/* HACK : 정의되지 않은 토큰이라 #D2D4D5를 그대로 사용 */}
<Certified width={62} color="#D2D4D5" />
{/* HACK: gray.90(#c7c9cb)에 대응하는 아이콘용 semantic 토큰 없음 */}
<Certified width={62} color="#c7c9cb" />
</S.LockedCharacterCard>
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,5 @@ export const Star = styled(SvgStar)<{ $active: boolean }>`
color: ${({ $active }) =>
$active
? getToken('fg.accent-yellow.normal')
: // HACK: 비활성 별 색상(구 gray[100]=#d2d4d5)에 대응하는 semantic 토큰이 없어 근사값 사용
getToken('fg.inverse.neutral')};
: getToken('fill.alternative')};
`;
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@ import { useState } from 'react';
import {
ActionArea,
BottomSheet,
Button,
Input,
Keypad,
KeyValue,
Chip,
PriceDisplay,
} from '@/shared/design-system/ui';
import {
Expand Down Expand Up @@ -86,18 +86,14 @@ function ExpenseAmountInput({
<PriceDisplay value={formattedInput} />
<S.QuickAddContainer>
{QUICK_ADD_BUTTONS.map(({ label, amount }) => (
<Button
<Chip
key={amount}
label={label}
variant="unselected"
onClick={() => handleQuickAdd(amount)}
variant="secondary"
size="small"
>
{label}
</Button>
/>
))}
<Button onClick={handleClearInput} variant="red" size="small">
전체삭제
</Button>
<Chip label="전체삭제" variant="red" onClick={handleClearInput} />
</S.QuickAddContainer>
<Keypad onPress={handleKeypadPress} />
<ActionArea
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import {
Accordion,
useAccordionContext,
NameChip,
Chip,
} from '@/shared/design-system/ui';
import SvgCarbonEdit from '@/shared/assets/svgs/icon/CarbonEdit';
import SvgClose from '@/shared/assets/svgs/icon/Close';
Expand Down Expand Up @@ -83,7 +83,7 @@ function SettlementSummary({
<Accordion.Content>
<S.MemberChipList>
{memberExpenses.map((member) => (
<NameChip
<Chip
key={member.id}
label={
member.role === 'MANAGER'
Expand Down
6 changes: 1 addition & 5 deletions src/pages/expenseDetail/ui/ExpenseMemberItem/index.style.ts
Original file line number Diff line number Diff line change
Expand Up @@ -146,11 +146,7 @@ export const TextButtonWrapper = styled.button<{ $isActive: boolean }>`
align-items: center;
cursor: pointer;
color: ${({ $isActive }) =>
$isActive
? getToken('fg.primary.normal')
: getToken(
'fill.normal-disable'
)}; /* HACK: #ACAFB2로 정의된 토큰이 없어 의미상 유사한 토큰을 임의로 사용함 */
$isActive ? getToken('fg.primary.normal') : getToken('fg.normal-disable')};
> svg {
display: ${({ $isActive }) => ($isActive ? 'block' : 'none')};
}
Expand Down
4 changes: 2 additions & 2 deletions src/pages/expenseDetail/ui/ExpenseTimelineContent/index.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { NameChip } from '@/shared/design-system/ui';
import { Chip } from '@/shared/design-system/ui';
import { ExpenseDetail } from '@/entities/expense/model/expense.type';
import * as S from './index.styles';

Expand All @@ -21,7 +21,7 @@ function ExpenseTimelineContent({ expense }: ExpenseTimelineContentProps) {
</S.MemberChipHeader>
<S.MemberChipList>
{expense.groupMembers.map((name) => (
<NameChip key={name} label={name} variant="black" size="s" />
<Chip key={name} label={name} variant="black" size="s" />
))}
</S.MemberChipList>
</S.MemberChipContainer>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@
import type { Meta, StoryObj } from '@storybook/react';
import { NameChip } from './NameChip';
import type { NameChipVariant, NameChipSize } from './NameChip';
import { Chip } from './Chip';
import type { ChipVariant, ChipSize } from './Chip';

const VARIANTS: NameChipVariant[] = [
const VARIANTS: ChipVariant[] = [
'selected',
'unselected',
'disabled',
'red',
'black',
];
const SIZES: NameChipSize[] = ['m', 's'];
const SIZES: ChipSize[] = ['m', 's'];

const meta: Meta<typeof NameChip> = {
title: 'Components/NameChip',
component: NameChip,
const meta: Meta<typeof Chip> = {
title: 'Components/Chip',
component: Chip,
tags: ['autodocs'],
argTypes: {
variant: {
Expand All @@ -36,7 +36,7 @@ const meta: Meta<typeof NameChip> = {
};

export default meta;
type Story = StoryObj<typeof NameChip>;
type Story = StoryObj<typeof Chip>;

export const Default: Story = {};

Expand All @@ -50,11 +50,30 @@ export const Showcase: Story = {
>
<span style={{ width: 16, fontSize: 12 }}>{size}</span>
{VARIANTS.map((variant) => (
<NameChip
<Chip key={variant} variant={variant} size={size} label="김모또" />
))}
</div>
))}
</div>
),
};

export const AsButton: Story = {
render: () => (
<div style={{ display: 'flex', flexDirection: 'column', gap: 16 }}>
{SIZES.map((size) => (
<div
key={size}
style={{ display: 'flex', gap: 8, alignItems: 'center' }}
>
<span style={{ width: 16, fontSize: 12 }}>{size}</span>
{VARIANTS.map((variant) => (
<Chip
key={variant}
variant={variant}
size={size}
label="김모또"
onClick={() => alert(`${variant} 클릭`)}
/>
))}
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,10 @@ import {
applyTypography,
} from '@/shared/design-system';

interface StyledNameChipProps {
interface StyledChipProps {
$variant: 'selected' | 'unselected' | 'disabled' | 'red' | 'black';
$size: 'm' | 's';
$clickable?: boolean;
}

// HACK: s size는 12px Medium이지만 해당 semantic token 없음.
Expand Down Expand Up @@ -52,13 +53,19 @@ const variantStyles = {
`,
};

export const Chip = styled.div<StyledNameChipProps>`
export const ChipRoot = styled.div<StyledChipProps>`
display: inline-flex;
align-items: center;
justify-content: center;
gap: ${getToken('gap.1')};
border-radius: ${getToken('radius.full')};
white-space: nowrap;
${({ $clickable }) =>
$clickable &&
css`
border: none;
cursor: pointer;
`}
${({ $size }) => sizeStyles[$size]}
${({ $variant }) => variantStyles[$variant]}
`;
31 changes: 31 additions & 0 deletions src/shared/design-system/ui/Chip/Chip.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import type { MouseEventHandler } from 'react';
import * as S from './Chip.styles';

type ChipVariant = 'selected' | 'unselected' | 'disabled' | 'red' | 'black';
type ChipSize = 'm' | 's';

interface ChipProps {
label: string;
variant?: ChipVariant;
size?: ChipSize;
onClick?: MouseEventHandler<HTMLButtonElement>;
}

function Chip(props: ChipProps) {
const { label, variant = 'selected', size = 'm', onClick } = props;

return (
<S.ChipRoot
as={onClick ? 'button' : 'div'}
$variant={variant}
$size={size}
$clickable={!!onClick}
onClick={onClick}
>
{label}
</S.ChipRoot>
);
}

export { Chip };
export type { ChipProps, ChipVariant, ChipSize };
2 changes: 2 additions & 0 deletions src/shared/design-system/ui/Chip/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export { Chip } from './Chip';
export type { ChipProps, ChipVariant, ChipSize } from './Chip';
20 changes: 20 additions & 0 deletions src/shared/design-system/ui/Input/Input.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ const meta: Meta<InputProps> = {
label: { control: 'text' },
required: { control: 'boolean' },
placeholder: { control: 'text' },
helpText: { control: 'text' },
value: { control: 'text' },
},
args: {
Expand Down Expand Up @@ -56,6 +57,25 @@ export const Showcase: Story = {
),
};

export const WithHelpText: Story = {
render: () => (
<div
style={{ display: 'flex', flexDirection: 'column', gap: 32, width: 350 }}
>
{STATES.map((state) => (
<Input
key={state}
label={state}
required
placeholder="placeholder"
state={state}
helpText="메시지를 입력해주세요."
/>
))}
</div>
),
};

export const PriceVariant: Story = {
render: () => (
<div
Expand Down
32 changes: 31 additions & 1 deletion src/shared/design-system/ui/Input/Input.styles.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
import styled, { css } from 'styled-components';
import { getToken, applyTypography } from '@/shared/design-system';
import {
getToken,
getTypographyToken,
applyTypography,
} from '@/shared/design-system';
import type { InputState, InputVariant } from './Input';

const defaultWrapperStateStyles = {
Expand Down Expand Up @@ -117,3 +121,29 @@ export const PriceUnit = styled.span`
color: ${getToken('fg.normal')};
flex-shrink: 0;
`;

export const HelpTextContainer = styled.div`
display: flex;
align-items: center;
gap: ${getToken('gap.1')};
`;

export const HelpTextIconWrapper = styled.span`
display: flex;
align-items: center;
flex-shrink: 0;
width: 18px;
height: 18px;
color: ${getToken('fg.accent-red.normal')};
`;

// HACK: 12px Medium에 해당하는 semantic token 없음.
// caption.xsmall(12px Regular)에 caption.small-medium의 font-weight(Medium)를 override해 적용.
// HACK: --text/default(#444950)에 대응하는 text용 semantic token 없음.
export const HelpText = styled.span<{ $state: InputState }>`
${applyTypography('typography.caption.xsmall')}
font-weight: ${getTypographyToken('typography.caption.small-medium')
.fontWeight};
color: ${({ $state }) =>
$state === 'error' ? getToken('fg.accent-red.normal') : '#444950'};
`;
14 changes: 13 additions & 1 deletion src/shared/design-system/ui/Input/Input.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { useId, forwardRef } from 'react';
import type { ReactNode, ChangeEventHandler, InputHTMLAttributes } from 'react';
import SvgSystemDanger from '@/shared/assets/svgs/icon/SystemDanger';
import * as S from './Input.styles';

type InputState = 'default' | 'error' | 'disabled';
Expand All @@ -11,6 +12,7 @@ interface InputProps
state?: InputState;
variant?: InputVariant;
trailingIcon?: ReactNode;
helpText?: string;
value?: string;
onChange?: ChangeEventHandler<HTMLInputElement>;
}
Expand All @@ -24,6 +26,7 @@ const Input = forwardRef<HTMLInputElement, InputProps>(function Input(
state = 'default',
variant = 'default',
trailingIcon,
helpText,
value,
onChange,
onClick,
Expand Down Expand Up @@ -67,7 +70,16 @@ const Input = forwardRef<HTMLInputElement, InputProps>(function Input(
</S.IconWrapper>
)}
</S.InputWrapper>
{/* TODO: 헬프텍스트 - 디자인 확정 후 추가 예정 */}
{helpText && variant === 'default' && (
<S.HelpTextContainer>
{state === 'error' && (
<S.HelpTextIconWrapper>
<SvgSystemDanger width={18} height={18} />
</S.HelpTextIconWrapper>
)}
<S.HelpText $state={state}>{helpText}</S.HelpText>
</S.HelpTextContainer>
)}
</S.Container>
);
});
Expand Down
24 changes: 0 additions & 24 deletions src/shared/design-system/ui/NameChip/NameChip.tsx

This file was deleted.

2 changes: 0 additions & 2 deletions src/shared/design-system/ui/NameChip/index.ts

This file was deleted.

Loading
Loading