= {
label: { control: 'text' },
required: { control: 'boolean' },
placeholder: { control: 'text' },
+ helpText: { control: 'text' },
value: { control: 'text' },
},
args: {
@@ -56,6 +57,25 @@ export const Showcase: Story = {
),
};
+export const WithHelpText: Story = {
+ render: () => (
+
+ {STATES.map((state) => (
+
+ ))}
+
+ ),
+};
+
export const PriceVariant: Story = {
render: () => (
`
+ ${applyTypography('typography.caption.xsmall')}
+ font-weight: ${getTypographyToken('typography.caption.small-medium')
+ .fontWeight};
+ color: ${({ $state }) =>
+ $state === 'error' ? getToken('fg.accent-red.normal') : '#444950'};
+`;
diff --git a/src/shared/design-system/ui/Input/Input.tsx b/src/shared/design-system/ui/Input/Input.tsx
index 225e201d..466112ad 100644
--- a/src/shared/design-system/ui/Input/Input.tsx
+++ b/src/shared/design-system/ui/Input/Input.tsx
@@ -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';
@@ -11,6 +12,7 @@ interface InputProps
state?: InputState;
variant?: InputVariant;
trailingIcon?: ReactNode;
+ helpText?: string;
value?: string;
onChange?: ChangeEventHandler;
}
@@ -24,6 +26,7 @@ const Input = forwardRef(function Input(
state = 'default',
variant = 'default',
trailingIcon,
+ helpText,
value,
onChange,
onClick,
@@ -67,7 +70,16 @@ const Input = forwardRef(function Input(
)}
- {/* TODO: 헬프텍스트 - 디자인 확정 후 추가 예정 */}
+ {helpText && variant === 'default' && (
+
+ {state === 'error' && (
+
+
+
+ )}
+ {helpText}
+
+ )}
);
});
diff --git a/src/shared/design-system/ui/NameChip/NameChip.tsx b/src/shared/design-system/ui/NameChip/NameChip.tsx
deleted file mode 100644
index 43d46248..00000000
--- a/src/shared/design-system/ui/NameChip/NameChip.tsx
+++ /dev/null
@@ -1,24 +0,0 @@
-import * as S from './NameChip.styles';
-
-// HACK : ExpenseTimelineContent 에서 사용하는 NameChip이 정의되어 있지 않아서 임의로 black variant를 추가함.
-type NameChipVariant = 'selected' | 'unselected' | 'disabled' | 'red' | 'black';
-type NameChipSize = 'm' | 's';
-
-interface NameChipProps {
- label: string;
- variant?: NameChipVariant;
- size?: NameChipSize;
-}
-
-function NameChip(props: NameChipProps) {
- const { label, variant = 'selected', size = 'm' } = props;
-
- return (
-
- {label}
-
- );
-}
-
-export { NameChip };
-export type { NameChipProps, NameChipVariant, NameChipSize };
diff --git a/src/shared/design-system/ui/NameChip/index.ts b/src/shared/design-system/ui/NameChip/index.ts
deleted file mode 100644
index 16086b1f..00000000
--- a/src/shared/design-system/ui/NameChip/index.ts
+++ /dev/null
@@ -1,2 +0,0 @@
-export { NameChip } from './NameChip';
-export type { NameChipProps, NameChipVariant, NameChipSize } from './NameChip';
diff --git a/src/shared/design-system/ui/index.ts b/src/shared/design-system/ui/index.ts
index 5cf32d89..017348c2 100644
--- a/src/shared/design-system/ui/index.ts
+++ b/src/shared/design-system/ui/index.ts
@@ -19,7 +19,8 @@ export { Keypad } from './Keypad';
export type { KeyValue } from './Keypad';
export { Modal } from './Modal';
export type { ModalProps } from './Modal';
-export { NameChip } from './NameChip';
+export { Chip } from './Chip';
+export type { ChipProps, ChipVariant, ChipSize } from './Chip';
export { PaidChip } from './PaidChip';
export type { PaidChipStatus } from './PaidChip';
export type { HeaderProps } from './Header';
diff --git a/src/shared/hooks/useApiError.ts b/src/shared/hooks/useApiError.ts
index c8c61276..57d2d9a3 100644
--- a/src/shared/hooks/useApiError.ts
+++ b/src/shared/hooks/useApiError.ts
@@ -31,22 +31,19 @@ const useApiError = ({
[errorHandlers]
);
- // customErrorHandlers를 우선 처리하고, defaultHandler를 처리합니다.
+ // ignoreBoundaryErrors에 포함된 에러만 핸들러를 실행합니다.
+ // 그 외 에러는 shouldThrowError가 true를 반환하여 ErrorBoundary에서 처리합니다.
const handleError = useCallback(
(error: TError) => {
if (isAxiosError(error)) {
const status = error.response?.status;
- if (status) {
+ if (status && ignoreBoundaryErrors?.includes(status)) {
const customHandler = handlers[status] || handlers.default;
customHandler();
- } else {
- handlers.default();
}
- } else {
- handlers.default();
}
},
- [handlers]
+ [handlers, ignoreBoundaryErrors]
);
// ignoreBoundaryErrors에 포함된 에러코드인지 확인합니다.
diff --git a/vite.config.ts b/vite.config.ts
index 58692b34..35b0aaa6 100644
--- a/vite.config.ts
+++ b/vite.config.ts
@@ -79,10 +79,13 @@ export default defineConfig(({ mode }) => {
clientsClaim: true,
runtimeCaching: [
{
- urlPattern: /\.(?:js|css|png|jpg|jpeg|svg)$/,
+ // same-origin 정적 자원만 캐시 (외부 S3 이미지는 제외 - CORS/tainted canvas 방지)
+ urlPattern: ({ url, sameOrigin }) =>
+ sameOrigin &&
+ /\.(?:js|css|png|jpg|jpeg|svg)$/.test(url.pathname),
handler: 'StaleWhileRevalidate', // 네트워크를 먼저 시도
options: {
- cacheName: 'static-assets',
+ cacheName: 'static-assets-v2',
expiration: {
maxEntries: 60, // 최대 캐시 항목 수
maxAgeSeconds: 30 * 24 * 60 * 60, // 30일