[Feat] #11 - 공통 InputBox 컴포넌트 추가#15
Conversation
📝 WalkthroughWalkthroughAdds a shared InputBox module (Input, Textarea, Select) with styling helpers, expands the icon library with seven SVG icons, adds Storybook stories for InputBox variants, and removes two GitHub issue templates. Changes
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches
🧪 Generate unit tests (beta)
Tip Try Coding Plans. Let us write the prompt for your AI agent so you can ship faster (with fewer bugs). Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 Fix all issues with AI agents
In `@src/shared/ui/inputBox/inputBox.tsx`:
- Around line 195-213: The icon-only buttons in inputBox.tsx lack accessible
labels; add aria-label attributes to the password toggle button (the button that
calls setIsPasswordVisible and renders EyeIcon/EyeOffIcon) and to the search
clear button (the button that calls onClear and renders XCircleIcon), using
descriptive text such as "Show password"/"Hide password" based on
isPasswordVisible for the password toggle and "Clear search" for the clear
button; ensure the labels are present only when the buttons render (type ===
"password" && !disabled and isSearch && props.value && onClear) so screen
readers receive meaningful descriptions.
- Around line 25-27: The Tailwind v4 breaking change means the important
modifier must be suffixed not prefixed, so update the class strings in the
inputBox export (the "error" and any similar class constants like
"defaultBorder") to move leading "!" to the end of the utility — e.g., change
instances like "!border-[color:var(--color-error-500,...)]" to
"border-[color:var(--color-error-500,...)]!" and "focus:!shadow-[...]" to
"focus:shadow-[...]!" so the important modifier is a trailing suffix on the
utility classes used in inputBox.tsx.
- Around line 251-280: The charCount state in the Textarea component doesn't
initialize or sync with incoming defaultValue/value, so set its initial state
from props (e.g., useState(() => ((props.value ?? props.defaultValue) as string
|| '').length)) and add a useEffect that updates charCount whenever props.value
or props.defaultValue changes; keep handleChange as-is to update on user input.
Ensure you reference the forwarded component Textarea, state variable charCount,
handler handleChange, and the incoming props (value/defaultValue/maxLength) when
implementing the initialization and sync.
| {type === "password" && !disabled && ( | ||
| <button | ||
| type="button" | ||
| onClick={() => setIsPasswordVisible(!isPasswordVisible)} | ||
| className="text-[color:var(--color-gray-600,#666666)] hover:text-[color:var(--color-primary-800,#004A9C)] focus:outline-none" | ||
| > | ||
| {isPasswordVisible ? <EyeOffIcon width={20} /> : <EyeIcon width={20} />} | ||
| </button> | ||
| )} | ||
|
|
||
| {isSearch && props.value && onClear && ( | ||
| <button | ||
| type="button" | ||
| onClick={onClear} | ||
| className="text-[color:var(--color-gray-600,#666666)] hover:text-[color:var(--color-gray-900,#1A1A1A)] focus:outline-none" | ||
| > | ||
| <XCircleIcon width={16} /> | ||
| </button> | ||
| )} |
There was a problem hiding this comment.
Add aria-labels for accessibility on icon buttons.
The password visibility toggle and search clear buttons lack accessible labels. Screen reader users won't understand what these buttons do.
♿ Proposed fix for accessibility
{type === "password" && !disabled && (
<button
type="button"
onClick={() => setIsPasswordVisible(!isPasswordVisible)}
+ aria-label={isPasswordVisible ? "비밀번호 숨기기" : "비밀번호 보기"}
className="text-[color:var(--color-gray-600,`#666666`)] hover:text-[color:var(--color-primary-800,`#004A9C`)] focus:outline-none"
>
{isPasswordVisible ? <EyeOffIcon width={20} /> : <EyeIcon width={20} />}
</button>
)}
{isSearch && props.value && onClear && (
<button
type="button"
onClick={onClear}
+ aria-label="검색어 지우기"
className="text-[color:var(--color-gray-600,`#666666`)] hover:text-[color:var(--color-gray-900,`#1A1A1A`)] focus:outline-none"
>
<XCircleIcon width={16} />
</button>
)}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| {type === "password" && !disabled && ( | |
| <button | |
| type="button" | |
| onClick={() => setIsPasswordVisible(!isPasswordVisible)} | |
| className="text-[color:var(--color-gray-600,#666666)] hover:text-[color:var(--color-primary-800,#004A9C)] focus:outline-none" | |
| > | |
| {isPasswordVisible ? <EyeOffIcon width={20} /> : <EyeIcon width={20} />} | |
| </button> | |
| )} | |
| {isSearch && props.value && onClear && ( | |
| <button | |
| type="button" | |
| onClick={onClear} | |
| className="text-[color:var(--color-gray-600,#666666)] hover:text-[color:var(--color-gray-900,#1A1A1A)] focus:outline-none" | |
| > | |
| <XCircleIcon width={16} /> | |
| </button> | |
| )} | |
| {type === "password" && !disabled && ( | |
| <button | |
| type="button" | |
| onClick={() => setIsPasswordVisible(!isPasswordVisible)} | |
| aria-label={isPasswordVisible ? "비밀번호 숨기기" : "비밀번호 보기"} | |
| className="text-[color:var(--color-gray-600,`#666666`)] hover:text-[color:var(--color-primary-800,`#004A9C`)] focus:outline-none" | |
| > | |
| {isPasswordVisible ? <EyeOffIcon width={20} /> : <EyeIcon width={20} />} | |
| </button> | |
| )} | |
| {isSearch && props.value && onClear && ( | |
| <button | |
| type="button" | |
| onClick={onClear} | |
| aria-label="검색어 지우기" | |
| className="text-[color:var(--color-gray-600,`#666666`)] hover:text-[color:var(--color-gray-900,`#1A1A1A`)] focus:outline-none" | |
| > | |
| <XCircleIcon width={16} /> | |
| </button> | |
| )} |
🤖 Prompt for AI Agents
In `@src/shared/ui/inputBox/inputBox.tsx` around lines 195 - 213, The icon-only
buttons in inputBox.tsx lack accessible labels; add aria-label attributes to the
password toggle button (the button that calls setIsPasswordVisible and renders
EyeIcon/EyeOffIcon) and to the search clear button (the button that calls
onClear and renders XCircleIcon), using descriptive text such as "Show
password"/"Hide password" based on isPasswordVisible for the password toggle and
"Clear search" for the clear button; ensure the labels are present only when the
buttons render (type === "password" && !disabled and isSearch && props.value &&
onClear) so screen readers receive meaningful descriptions.
|
PR제목도 컨벤션 맞춰서 수정 부탁드려요~ |
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Fix all issues with AI agents
In `@src/shared/ui/inputBox/inputBox.stories.tsx`:
- Around line 80-90: The Search story provides onClear but doesn't set a value
so the clear button won't render; update the Search Story (export const Search)
to include a non-empty value (or defaultValue) in args (e.g., value: "example")
so the component's condition that checks props.value && onClear becomes truthy
and the clear button is visible; keep the existing onClear handler to
demonstrate the clear behavior.
In `@src/shared/ui/inputBox/inputBox.tsx`:
- Around line 71-89: getTextareaClasses is missing support for the disabled
visual state—add a disabled boolean parameter (matching
getInputClasses/getSelectClasses signature), compute a disabledClass variable
that uses commonStyles.disabled when disabled is true, and include that
disabledClass in the returned class list (ensuring it is applied alongside/after
stateClass so disabled styling takes precedence). Update any call sites that
render <textarea> to pass the disabled flag where appropriate.
🧹 Nitpick comments (1)
src/shared/ui/inputBox/inputBox.tsx (1)
283-314: Potential controlled/uncontrolled conflict in Select.When
placeholderis provided,defaultValue=""is set unconditionally. If a consumer also passes avalueprop (controlled mode), React may warn about switching between controlled and uncontrolled states, or thedefaultValuebecomes ineffective.Consider conditionally applying
defaultValueonly when not in controlled mode:🔧 Proposed fix
<select ref={ref} className={selectClasses} - defaultValue={placeholder ? "" : undefined} + defaultValue={placeholder && props.value === undefined ? "" : undefined} {...props} >
| // 검색 입력 (Rounded Pill Shape) | ||
| export const Search: Story = { | ||
| args: { | ||
| type: "search", | ||
| placeholder: "검색어를 입력하세요...", | ||
| size: "medium", | ||
| isFullWidth: true, | ||
| // onClear가 전달되면 값이 있을 때 X 버튼이 자동으로 노출됨 | ||
| onClear: () => alert("Clear Clicked!"), | ||
| }, | ||
| }; |
There was a problem hiding this comment.
Clear button won't be visible without a value prop.
The onClear callback is provided but the clear button only renders when props.value && onClear is truthy (per component logic at line 188 in inputBox.tsx). Without a value, users won't see the clear functionality in this story.
🔧 Proposed fix to demonstrate clear functionality
export const Search: Story = {
args: {
type: "search",
placeholder: "검색어를 입력하세요...",
size: "medium",
isFullWidth: true,
+ value: "검색어 예시",
// onClear가 전달되면 값이 있을 때 X 버튼이 자동으로 노출됨
onClear: () => alert("Clear Clicked!"),
},
};📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| // 검색 입력 (Rounded Pill Shape) | |
| export const Search: Story = { | |
| args: { | |
| type: "search", | |
| placeholder: "검색어를 입력하세요...", | |
| size: "medium", | |
| isFullWidth: true, | |
| // onClear가 전달되면 값이 있을 때 X 버튼이 자동으로 노출됨 | |
| onClear: () => alert("Clear Clicked!"), | |
| }, | |
| }; | |
| // 검색 입력 (Rounded Pill Shape) | |
| export const Search: Story = { | |
| args: { | |
| type: "search", | |
| placeholder: "검색어를 입력하세요...", | |
| size: "medium", | |
| isFullWidth: true, | |
| value: "검색어 예시", | |
| // onClear가 전달되면 값이 있을 때 X 버튼이 자동으로 노출됨 | |
| onClear: () => alert("Clear Clicked!"), | |
| }, | |
| }; |
🤖 Prompt for AI Agents
In `@src/shared/ui/inputBox/inputBox.stories.tsx` around lines 80 - 90, The Search
story provides onClear but doesn't set a value so the clear button won't render;
update the Search Story (export const Search) to include a non-empty value (or
defaultValue) in args (e.g., value: "example") so the component's condition that
checks props.value && onClear becomes truthy and the clear button is visible;
keep the existing onClear handler to demonstrate the clear behavior.
| const getTextareaClasses = ( | ||
| hasError: boolean, | ||
| isFullWidth: boolean, | ||
| className?: string, | ||
| ): string => { | ||
| const stateClass = hasError ? commonStyles.error : commonStyles.defaultBorder; | ||
| const widthClass = isFullWidth ? "w-full" : "w-auto"; | ||
|
|
||
| return [ | ||
| commonStyles.base, | ||
| "min-h-[120px] p-3 text-[16px] leading-[24px] rounded-lg bg-white resize-y", | ||
| commonStyles.focus, | ||
| stateClass, | ||
| widthClass, | ||
| className, | ||
| ] | ||
| .filter(Boolean) | ||
| .join(" "); | ||
| }; |
There was a problem hiding this comment.
Missing disabled styling for Textarea.
getTextareaClasses doesn't include commonStyles.disabled, unlike getInputClasses (line 58) and getSelectClasses (line 97). Disabled textareas will lack the proper visual feedback.
🐛 Proposed fix to add disabled styles
const getTextareaClasses = (
hasError: boolean,
isFullWidth: boolean,
className?: string,
): string => {
const stateClass = hasError ? commonStyles.error : commonStyles.defaultBorder;
const widthClass = isFullWidth ? "w-full" : "w-auto";
return [
commonStyles.base,
+ commonStyles.disabled,
"min-h-[120px] p-3 text-[16px] leading-[24px] rounded-lg bg-white resize-y",
commonStyles.focus,
stateClass,
widthClass,
className,
]
.filter(Boolean)
.join(" ");
};🤖 Prompt for AI Agents
In `@src/shared/ui/inputBox/inputBox.tsx` around lines 71 - 89, getTextareaClasses
is missing support for the disabled visual state—add a disabled boolean
parameter (matching getInputClasses/getSelectClasses signature), compute a
disabledClass variable that uses commonStyles.disabled when disabled is true,
and include that disabledClass in the returned class list (ensuring it is
applied alongside/after stateClass so disabled styling takes precedence). Update
any call sites that render <textarea> to pass the disabled flag where
appropriate.
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/shared/ui/inputBox/inputBox.tsx`:
- Line 194: The XCircleIcon is passed width={16} which only overrides the SVG
width, leaving height at the default 20; change the usage in inputBox.tsx to
pass size={16} (or set both width and height to 16) so the icon renders square.
Locate the instance of XCircleIcon in the InputBox component (symbol:
XCircleIcon) and replace width={16} with size={16} (or width={16} height={16})
to ensure both dimensions match.
- Around line 140-142: The computed boolean hasRightContent incorrectly includes
password buttons even when the input is disabled; update the expression that
sets hasRightContent to only count the password toggle when the input is not
disabled (e.g., change the password check from type === "password" to type ===
"password" && !disabled) so rightIcon, isSearch && props.value && onClear, and
the password toggle (guarded by disabled) are consistently reflected by
hasRightContent; adjust the expression where hasRightContent is declared to
reference the existing symbols (rightIcon, type, isSearch, props.value, onClear,
disabled) accordingly.
- Around line 283-294: The Select component currently always sets
defaultValue="" when placeholder is present which, when combined with a
consumer-supplied value prop, causes React's controlled/uncontrolled warning;
fix this in Select by computing and applying value-related props conditionally:
inspect props for existing "value" or "defaultValue" and if either exists do not
set a defaultValue, otherwise if placeholder is provided set defaultValue to ""
(or leave undefined when no placeholder); then spread these computed value props
and finally spread the remaining {...props} into the <select> so you never end
up with both a consumer "value" and an internal defaultValue.
---
Duplicate comments:
In `@src/shared/ui/inputBox/inputBox.tsx`:
- Around line 178-196: Add accessible labels to the icon-only buttons: for the
password toggle in the block using setIsPasswordVisible and isPasswordVisible
(and rendering EyeIcon/EyeOffIcon) add a dynamic aria-label that reads "Show
password" when isPasswordVisible is false and "Hide password" when true, and
also include aria-pressed={isPasswordVisible} to convey toggle state; for the
search clear button that uses isSearch, props.value and onClear (rendering
XCircleIcon) add an aria-label="Clear search" so screen readers can identify the
control. Ensure the attributes are present on the same <button> elements already
using type="button".
- Around line 71-89: getTextareaClasses is missing support for disabled styling;
update its signature to accept an isDisabled boolean (e.g.,
getTextareaClasses(hasError, isFullWidth, isDisabled, className)), compute a
disabledClass = isDisabled ? commonStyles.disabled : null, and when isDisabled
is true use disabledClass in place of the error/default state (i.e., don't apply
stateClass when disabled). Add disabledClass into the returned classes array
alongside commonStyles.base, commonStyles.focus, widthClass, etc., then filter
and join as before so disabled textareas render the same visual state as
Input/Select.
- Around line 18-19: commonStyles.error uses Tailwind v3-style leading "!"
modifiers which are invalid in Tailwind v4; update the utilities so the
important marker is a trailing suffix instead. Replace
"!border-[color:var(--color-error-500,`#EF4444`)]" with
"border-[color:var(--color-error-500,`#EF4444`)]!" and replace
"focus:!shadow-[0_0_0_3px_var(--color-error-100,`#FEE2E2`)]" with
"focus:shadow-[0_0_0_3px_var(--color-error-100,`#FEE2E2`)]!" in the
commonStyles.error definition in inputBox.tsx so the important modifier follows
Tailwind v4 syntax.
| const hasRightContent = Boolean( | ||
| rightIcon || type === "password" || (isSearch && props.value && onClear), | ||
| ); |
There was a problem hiding this comment.
hasRightContent incorrectly adds right padding for disabled password inputs.
type === "password" is unconditionally true, so right padding is always applied. However, the password toggle button is guarded by !disabled (Line 178), so no icon is rendered—leaving the input with spurious right padding when disabled.
🐛 Proposed fix
const hasRightContent = Boolean(
- rightIcon || type === "password" || (isSearch && props.value && onClear),
+ rightIcon || (type === "password" && !disabled) || (isSearch && props.value && onClear),
);📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const hasRightContent = Boolean( | |
| rightIcon || type === "password" || (isSearch && props.value && onClear), | |
| ); | |
| const hasRightContent = Boolean( | |
| rightIcon || (type === "password" && !disabled) || (isSearch && props.value && onClear), | |
| ); |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/shared/ui/inputBox/inputBox.tsx` around lines 140 - 142, The computed
boolean hasRightContent incorrectly includes password buttons even when the
input is disabled; update the expression that sets hasRightContent to only count
the password toggle when the input is not disabled (e.g., change the password
check from type === "password" to type === "password" && !disabled) so
rightIcon, isSearch && props.value && onClear, and the password toggle (guarded
by disabled) are consistently reflected by hasRightContent; adjust the
expression where hasRightContent is declared to reference the existing symbols
(rightIcon, type, isSearch, props.value, onClear, disabled) accordingly.
| onClick={onClear} | ||
| className="text-[color:var(--color-gray-600,#666666)] hover:text-[color:var(--color-gray-900,#1A1A1A)] focus:outline-none" | ||
| > | ||
| <XCircleIcon width={16} /> |
There was a problem hiding this comment.
<XCircleIcon width={16} /> renders non-square (16 × 20).
Icon components use { size = 20, ...props } with width={size} and height={size} on the SVG, followed by {...props}. Passing width={16} overrides only width, leaving height at its default of 20. Use size instead:
🐛 Proposed fix
- <XCircleIcon width={16} />
+ <XCircleIcon size={16} />📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| <XCircleIcon width={16} /> | |
| <XCircleIcon size={16} /> |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/shared/ui/inputBox/inputBox.tsx` at line 194, The XCircleIcon is passed
width={16} which only overrides the SVG width, leaving height at the default 20;
change the usage in inputBox.tsx to pass size={16} (or set both width and height
to 16) so the icon renders square. Locate the instance of XCircleIcon in the
InputBox component (symbol: XCircleIcon) and replace width={16} with size={16}
(or width={16} height={16}) to ensure both dimensions match.
| export const Select = React.forwardRef<HTMLSelectElement, SelectProps>( | ||
| ({ hasError, isFullWidth = true, options, placeholder, className = "", ...props }, ref) => { | ||
| const selectClasses = getSelectClasses(!!hasError, !!isFullWidth, className); | ||
| const containerClasses = `relative ${isFullWidth ? "w-full" : "w-auto"}`; | ||
|
|
||
| return ( | ||
| <div className={containerClasses}> | ||
| <select | ||
| ref={ref} | ||
| className={selectClasses} | ||
| defaultValue={placeholder ? "" : undefined} | ||
| {...props} |
There was a problem hiding this comment.
Select emits a React warning when used as a controlled component with a placeholder.
Setting defaultValue="" before spreading {...props} means a consumer who passes value ends up with both value and defaultValue on the underlying <select>, triggering React's controlled/uncontrolled mixing warning.
🐛 Proposed fix
export const Select = React.forwardRef<HTMLSelectElement, SelectProps>(
- ({ hasError, isFullWidth = true, options, placeholder, className = "", ...props }, ref) => {
+ ({ hasError, isFullWidth = true, options, placeholder, className = "", value, ...props }, ref) => {
const selectClasses = getSelectClasses(!!hasError, !!isFullWidth, className);
const containerClasses = `relative ${isFullWidth ? "w-full" : "w-auto"}`;
return (
<div className={containerClasses}>
<select
ref={ref}
className={selectClasses}
- defaultValue={placeholder ? "" : undefined}
- {...props}
+ value={value}
+ {...(value === undefined ? { defaultValue: placeholder ? "" : undefined } : {})}
+ {...props}
>🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/shared/ui/inputBox/inputBox.tsx` around lines 283 - 294, The Select
component currently always sets defaultValue="" when placeholder is present
which, when combined with a consumer-supplied value prop, causes React's
controlled/uncontrolled warning; fix this in Select by computing and applying
value-related props conditionally: inspect props for existing "value" or
"defaultValue" and if either exists do not set a defaultValue, otherwise if
placeholder is provided set defaultValue to "" (or leave undefined when no
placeholder); then spread these computed value props and finally spread the
remaining {...props} into the <select> so you never end up with both a consumer
"value" and an internal defaultValue.
dd8a233 to
b65c8f5
Compare
There was a problem hiding this comment.
Actionable comments posted: 2
♻️ Duplicate comments (10)
src/shared/ui/inputBox/inputBox.tsx (10)
195-195:<XCircleIcon width={16} />still renders non-square (16 × 20).The icon spreads
{...props}after the explicitheight={size}on the SVG element, so passingwidth={16}only overrides width while height remains at the default20. Usesize={16}to set both dimensions uniformly.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/shared/ui/inputBox/inputBox.tsx` at line 195, The XCircleIcon is rendered non-square because inputBox.tsx passes width={16} and the component spreads {...props} after height={size}, so height stays 20; update the usage in inputBox.tsx to pass size={16} (not width) when rendering <XCircleIcon> so both width and height are set uniformly by the icon component.
141-143:hasRightContentstill incorrectly adds right padding for disabled password inputs.
type === "password"is unconditionally included, but the password toggle button is guarded by!disabled(Line 179). A disabled password<Input>will receive spurious right padding with no icon to fill it.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/shared/ui/inputBox/inputBox.tsx` around lines 141 - 143, The hasRightContent calculation adds padding for password fields even when disabled; update the Boolean expression used to compute hasRightContent (the const hasRightContent) to only include the password case when the input is not disabled (i.e., include a !disabled guard alongside type === "password"); keep the existing checks for rightIcon, isSearch && props.value && onClear unchanged so the password toggle padding is only applied when the password toggle button will actually render.
195-195:<XCircleIcon width={16} />still renders non-square (16 × 20).The icon API spreads
{...props}afterheight={size}, so onlywidthis overridden to 16 whileheightstays at the default 20. Usesize={16}to set both dimensions uniformly.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/shared/ui/inputBox/inputBox.tsx` at line 195, The XCircleIcon is being rendered non-square because width={16} overrides only width while the component's props set height after, so change the usage in inputBox.tsx from passing width={16} to using size={16} on the XCircleIcon component to ensure both width and height are set to 16 (locate the XCircleIcon JSX in the inputBox.tsx file).
179-196: Password toggle and clear buttons still lackaria-label.Both icon-only buttons remain without accessible labels, leaving screen-reader users unable to identify their purpose.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/shared/ui/inputBox/inputBox.tsx` around lines 179 - 196, Add accessible labels to the icon-only buttons: for the password visibility toggle (the button that calls setIsPasswordVisible and renders EyeIcon/EyeOffIcon) add an appropriate aria-label that reflects the action/state (e.g., "Show password" when isPasswordVisible is false and "Hide password" when true); for the clear button (the button that calls onClear and renders XCircleIcon) add an aria-label such as "Clear search" or "Clear input". Ensure the labels update to match state where applicable and leave the existing click handlers (setIsPasswordVisible, onClear) and icons unchanged.
71-89:getTextareaClassesstill omitscommonStyles.disabled.
commonStyles.disabledis included in bothgetInputClasses(line 58) andgetSelectClasses(line 97), but remains absent here. Disabled textareas will not receive the grayed-out cursor-not-allowed styling.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/shared/ui/inputBox/inputBox.tsx` around lines 71 - 89, getTextareaClasses currently omits commonStyles.disabled so disabled textareas don't get the grayed-out/cursor-not-allowed styling; update getTextareaClasses (and its call sites) to accept an isDisabled boolean (matching getInputClasses/getSelectClasses) and include commonStyles.disabled in the returned class list when isDisabled is true, preserving existing hasError and isFullWidth behavior for consistency with the other helpers.
285-296:Selectstill triggers a React controlled/uncontrolled mixing warning.
defaultValue={placeholder ? "" : undefined}is placed before{...props}, so a consumer passing avalueprop ends up with bothvalueanddefaultValueon the underlying<select>, which React flags as an illegal controlled/uncontrolled transition.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/shared/ui/inputBox/inputBox.tsx` around lines 285 - 296, The Select component is causing a controlled/uncontrolled warning because defaultValue is set before props are spread, so a consumer-supplied value prop can be passed alongside defaultValue; update Select so defaultValue is only applied when the consumer did not supply a value—inspect the forwarded props in the Select function (the {...props} object) and conditionally set defaultValue based on whether props.value is undefined (or move defaultValue after spreading props and only add it when no value exists), referencing the Select component and the defaultValue / {...props} usage to locate the change.
141-143:hasRightContentstill unconditionally includes the password toggle whendisabled.
type === "password"is not guarded by!disabled, so right padding is applied even though the toggle button is hidden (Line 179:type === "password" && !disabled).🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/shared/ui/inputBox/inputBox.tsx` around lines 141 - 143, The computed boolean hasRightContent incorrectly includes the password toggle even when the input is disabled; update the expression that builds hasRightContent to only consider the password toggle when type === "password" && !disabled (keep existing checks for rightIcon and the search clear behavior using isSearch, props.value and onClear unchanged), so padding only appears when the toggle is actually rendered; adjust the hasRightContent declaration (and any related usage) accordingly.
285-296:Selectstill triggers a React controlled/uncontrolled mixing warning.
defaultValue={placeholder ? "" : undefined}is set before{...props}, so a consumer who passesvalueends up with bothvalueanddefaultValueon the underlying<select>, which React flags as a controlled/uncontrolled conflict.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/shared/ui/inputBox/inputBox.tsx` around lines 285 - 296, The Select component sets defaultValue before spreading {...props}, causing defaultValue to collide with a consumer-supplied value and trigger controlled/uncontrolled warnings; update the Select render so that defaultValue is only applied when the consumer has not provided a controlled value (i.e., props.value is undefined) or move the defaultValue application after spreading props so a provided value will override it; locate the React.forwardRef Select function and adjust the defaultValue logic around the spread of {...props} (referencing Select, props, defaultValue, and getSelectClasses) to ensure defaultValue is omitted when props.value exists.
179-196: Password toggle and clear buttons still lackaria-label.Both interactive buttons remain without accessible labels, leaving screen-reader users without context for what the buttons do.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/shared/ui/inputBox/inputBox.tsx` around lines 179 - 196, The password-toggle and clear buttons in inputBox.tsx lack accessible labels; update the button for toggling password visibility (where setIsPasswordVisible and isPasswordVisible are used) to include an aria-label that reflects state (e.g., "Show password" when isPasswordVisible is false and "Hide password" when true), and add an aria-label to the search clear button (where isSearch, props.value and onClear are used) such as "Clear search input"; ensure these labels are localized/consistent with other UI text if applicable.
71-89:getTextareaClassesstill omitscommonStyles.disabled.
commonStyles.disabledis included in bothgetInputClasses(line 58) andgetSelectClasses(line 97), but is absent here. Disabled textareas will not receive grayed-out orcursor-not-allowedstyling.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/shared/ui/inputBox/inputBox.tsx` around lines 71 - 89, getTextareaClasses is missing inclusion of commonStyles.disabled so disabled textareas don't get the grayed/cursor-not-allowed styling; update getTextareaClasses to include commonStyles.disabled the same way getInputClasses and getSelectClasses do (add commonStyles.disabled into the returned class array alongside commonStyles.base/commonStyles.focus/stateClass/widthClass) and ensure it is added conditionally via the same truthy filter pattern so existing behavior is preserved.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/shared/ui/inputBox/inputBox.tsx`:
- Around line 18-20: The CSS arbitrary-value fallback strings in inputBox.tsx
are using backticks and produce invalid CSS; update the error and defaultBorder
string values (the error and defaultBorder constants) to remove the backtick
characters from the var() fallback values (e.g., change var(--color-error-500,
`#EF4444`) to var(--color-error-500, `#EF4444`) and var(--color-gray-300,
`#CCCCCC`) to var(--color-gray-300, `#CCCCCC`)) so they match the valid style used
in commonStyles.base/disabled/focus.
- Around line 18-20: In the commonStyles object (in inputBox.tsx) remove the
literal backticks inside the CSS arbitrary-value fallbacks so the Tailwind var()
fallbacks are valid: update the error property string used in commonStyles (the
value containing border-[color:var(--color-error-500,`#EF4444`)]! and
focus:shadow-[0_0_0_3px_var(--color-error-100,`#FEE2E2`)]!) to use
var(--color-error-500,`#EF4444`) and var(--color-error-100,`#FEE2E2`) without
backticks, and update defaultBorder
(border-[color:var(--color-gray-300,`#CCCCCC`)] ) to use
var(--color-gray-300,`#CCCCCC`) so the class names produce valid CSS/Tailwind
arbitrary values.
---
Duplicate comments:
In `@src/shared/ui/inputBox/inputBox.tsx`:
- Line 195: The XCircleIcon is rendered non-square because inputBox.tsx passes
width={16} and the component spreads {...props} after height={size}, so height
stays 20; update the usage in inputBox.tsx to pass size={16} (not width) when
rendering <XCircleIcon> so both width and height are set uniformly by the icon
component.
- Around line 141-143: The hasRightContent calculation adds padding for password
fields even when disabled; update the Boolean expression used to compute
hasRightContent (the const hasRightContent) to only include the password case
when the input is not disabled (i.e., include a !disabled guard alongside type
=== "password"); keep the existing checks for rightIcon, isSearch && props.value
&& onClear unchanged so the password toggle padding is only applied when the
password toggle button will actually render.
- Line 195: The XCircleIcon is being rendered non-square because width={16}
overrides only width while the component's props set height after, so change the
usage in inputBox.tsx from passing width={16} to using size={16} on the
XCircleIcon component to ensure both width and height are set to 16 (locate the
XCircleIcon JSX in the inputBox.tsx file).
- Around line 179-196: Add accessible labels to the icon-only buttons: for the
password visibility toggle (the button that calls setIsPasswordVisible and
renders EyeIcon/EyeOffIcon) add an appropriate aria-label that reflects the
action/state (e.g., "Show password" when isPasswordVisible is false and "Hide
password" when true); for the clear button (the button that calls onClear and
renders XCircleIcon) add an aria-label such as "Clear search" or "Clear input".
Ensure the labels update to match state where applicable and leave the existing
click handlers (setIsPasswordVisible, onClear) and icons unchanged.
- Around line 71-89: getTextareaClasses currently omits commonStyles.disabled so
disabled textareas don't get the grayed-out/cursor-not-allowed styling; update
getTextareaClasses (and its call sites) to accept an isDisabled boolean
(matching getInputClasses/getSelectClasses) and include commonStyles.disabled in
the returned class list when isDisabled is true, preserving existing hasError
and isFullWidth behavior for consistency with the other helpers.
- Around line 285-296: The Select component is causing a controlled/uncontrolled
warning because defaultValue is set before props are spread, so a
consumer-supplied value prop can be passed alongside defaultValue; update Select
so defaultValue is only applied when the consumer did not supply a value—inspect
the forwarded props in the Select function (the {...props} object) and
conditionally set defaultValue based on whether props.value is undefined (or
move defaultValue after spreading props and only add it when no value exists),
referencing the Select component and the defaultValue / {...props} usage to
locate the change.
- Around line 141-143: The computed boolean hasRightContent incorrectly includes
the password toggle even when the input is disabled; update the expression that
builds hasRightContent to only consider the password toggle when type ===
"password" && !disabled (keep existing checks for rightIcon and the search clear
behavior using isSearch, props.value and onClear unchanged), so padding only
appears when the toggle is actually rendered; adjust the hasRightContent
declaration (and any related usage) accordingly.
- Around line 285-296: The Select component sets defaultValue before spreading
{...props}, causing defaultValue to collide with a consumer-supplied value and
trigger controlled/uncontrolled warnings; update the Select render so that
defaultValue is only applied when the consumer has not provided a controlled
value (i.e., props.value is undefined) or move the defaultValue application
after spreading props so a provided value will override it; locate the
React.forwardRef Select function and adjust the defaultValue logic around the
spread of {...props} (referencing Select, props, defaultValue, and
getSelectClasses) to ensure defaultValue is omitted when props.value exists.
- Around line 179-196: The password-toggle and clear buttons in inputBox.tsx
lack accessible labels; update the button for toggling password visibility
(where setIsPasswordVisible and isPasswordVisible are used) to include an
aria-label that reflects state (e.g., "Show password" when isPasswordVisible is
false and "Hide password" when true), and add an aria-label to the search clear
button (where isSearch, props.value and onClear are used) such as "Clear search
input"; ensure these labels are localized/consistent with other UI text if
applicable.
- Around line 71-89: getTextareaClasses is missing inclusion of
commonStyles.disabled so disabled textareas don't get the
grayed/cursor-not-allowed styling; update getTextareaClasses to include
commonStyles.disabled the same way getInputClasses and getSelectClasses do (add
commonStyles.disabled into the returned class array alongside
commonStyles.base/commonStyles.focus/stateClass/widthClass) and ensure it is
added conditionally via the same truthy filter pattern so existing behavior is
preserved.
| error: | ||
| "border-[color:var(--color-error-500,`#EF4444`)]! focus:shadow-[0_0_0_3px_var(--color-error-100,`#FEE2E2`)]!", | ||
| defaultBorder: "border-[color:var(--color-gray-300,`#CCCCCC`)]", |
There was a problem hiding this comment.
Backtick characters in CSS arbitrary-value fallbacks produce invalid CSS.
Lines 19 and 20 embed backtick characters inside the var() fallback values:
"border-[color:var(--color-error-500,`#EF4444`)]!"
"border-[color:var(--color-gray-300,`#CCCCCC`)]"
Backtick (`) is not a valid CSS syntax character. Tailwind does minimal interpretation of arbitrary values — it simply injects the literal value into the generated rule. This means Tailwind will emit var(--color-error-500, #EF4444) verbatim into the stylesheet, which browsers will reject as an invalid value, causing the error border and the default border color to silently fail whenever the CSS custom property is not defined. Compare with the correctly written commonStyles.base, disabled, and focus constants on lines 13–17, which all use bare hex values without backticks.
Note: the previous review's proposed fix for the ! suffix also retained these backticks, so the issue was never resolved.
🐛 Proposed fix: remove backtick characters from fallback color values
error:
- "border-[color:var(--color-error-500,`#EF4444`)]! focus:shadow-[0_0_0_3px_var(--color-error-100,`#FEE2E2`)]!",
- defaultBorder: "border-[color:var(--color-gray-300,`#CCCCCC`)]",
+ "border-[color:var(--color-error-500,`#EF4444`)]! focus:shadow-[0_0_0_3px_var(--color-error-100,`#FEE2E2`)]!",
+ defaultBorder: "border-[color:var(--color-gray-300,`#CCCCCC`)]",🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/shared/ui/inputBox/inputBox.tsx` around lines 18 - 20, The CSS
arbitrary-value fallback strings in inputBox.tsx are using backticks and produce
invalid CSS; update the error and defaultBorder string values (the error and
defaultBorder constants) to remove the backtick characters from the var()
fallback values (e.g., change var(--color-error-500, `#EF4444`) to
var(--color-error-500, `#EF4444`) and var(--color-gray-300, `#CCCCCC`) to
var(--color-gray-300, `#CCCCCC`)) so they match the valid style used in
commonStyles.base/disabled/focus.
🧩 Analysis chain
🏁 Script executed:
fd "inputBox.tsx" --type fRepository: ajou-industry-matching/aim-frontend
Length of output: 114
🏁 Script executed:
cat -n src/shared/ui/inputBox/inputBox.tsx | head -30Repository: ajou-industry-matching/aim-frontend
Length of output: 1508
🌐 Web query:
Tailwind CSS v4 arbitrary values backtick syntax color fallback
💡 Result:
-
The backticks you’re seeing in Tailwind docs (for example
text-[]) are documentation formatting to indicate a placeholder — you don’t type backticks in your class name. (tailwindcss.com) -
Tailwind v4 arbitrary color value syntax is still:
text-[#50d71e](custom literal) (tailwindcss.com)text-(--my-color)as shorthand fortext-[var(--my-color)](CSS variable) (tailwindcss.com)
-
If you want a CSS-variable fallback color, put it inside
var()yourself using an arbitrary value:- Non-ambiguous utilities:
bg-[var(--brand,#0ea5e9)]
- For
text-(can be font-size or color), add Tailwind’s type hint:text-[color:var(--brand,#0ea5e9)](tailwindcss.com)
- Non-ambiguous utilities:
-
The v4 shorthand form
text-(--my-color)is only forvar(--my-color); if you need a fallback, use the bracket form shown above. (tailwindcss.com)
Citations:
- 1: https://tailwindcss.com/docs/color?utm_source=openai
- 2: https://tailwindcss.com/docs/color?utm_source=openai
- 3: https://tailwindcss.com/docs/color?utm_source=openai
- 4: https://tailwindcss.com/blog/tailwindcss-2-2?utm_source=openai
- 5: https://tailwindcss.com/docs/color?utm_source=openai
Remove backtick characters from CSS arbitrary-value fallbacks in commonStyles.
Lines 19–20 contain literal backticks inside the var() fallback values:
"border-[color:var(--color-error-500,`#EF4444`)]!"
"border-[color:var(--color-gray-300,`#CCCCCC`)]"
Backticks are not valid in CSS or Tailwind arbitrary values; they are generated as literal characters in the class name and break the color fallback syntax. The correct syntax is var(--color-error-500,#EF4444) without backticks.
Fix
error:
- "border-[color:var(--color-error-500,`#EF4444`)]! focus:shadow-[0_0_0_3px_var(--color-error-100,`#FEE2E2`)]!",
- defaultBorder: "border-[color:var(--color-gray-300,`#CCCCCC`)]",
+ "border-[color:var(--color-error-500,`#EF4444`)]! focus:shadow-[0_0_0_3px_var(--color-error-100,`#FEE2E2`)]!",
+ defaultBorder: "border-[color:var(--color-gray-300,`#CCCCCC`)]",🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/shared/ui/inputBox/inputBox.tsx` around lines 18 - 20, In the
commonStyles object (in inputBox.tsx) remove the literal backticks inside the
CSS arbitrary-value fallbacks so the Tailwind var() fallbacks are valid: update
the error property string used in commonStyles (the value containing
border-[color:var(--color-error-500,`#EF4444`)]! and
focus:shadow-[0_0_0_3px_var(--color-error-100,`#FEE2E2`)]!) to use
var(--color-error-500,`#EF4444`) and var(--color-error-100,`#FEE2E2`) without
backticks, and update defaultBorder
(border-[color:var(--color-gray-300,`#CCCCCC`)] ) to use
var(--color-gray-300,`#CCCCCC`) so the class names produce valid CSS/Tailwind
arbitrary values.
🔎 What is this PR?
공통 inputBox 컴포넌트를 구현하고 Storybook 문서화를 완료
📝 Changes
📸 Screenshots (선택)
🔗 Related Issue
closes #11
✔ Checklist
pnpm build)pnpm lint)🙏 Request
리뷰 요청 파일
Summary by CodeRabbit
New Features
Chores