@uniai-fe/uds-primitives 0.3.60 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (82) hide show
  1. package/README.md +209 -3
  2. package/dist/styles.css +20 -6
  3. package/package.json +1 -1
  4. package/src/components/alternate/index.tsx +7 -1
  5. package/src/components/alternate/markup/Label.tsx +10 -5
  6. package/src/components/alternate/markup/empty/Data.tsx +9 -6
  7. package/src/components/alternate/markup/index.tsx +8 -0
  8. package/src/components/alternate/markup/loading/Default.tsx +10 -6
  9. package/src/components/alternate/markup/loading/Icon.tsx +11 -4
  10. package/src/components/alternate/types/index.ts +75 -2
  11. package/src/components/badge/index.tsx +4 -1
  12. package/src/components/badge/markup/Badge.tsx +10 -8
  13. package/src/components/badge/types/index.ts +26 -2
  14. package/src/components/button/index.tsx +6 -1
  15. package/src/components/button/markup/Base.tsx +20 -18
  16. package/src/components/button/markup/Rounded.tsx +7 -4
  17. package/src/components/button/markup/Text.tsx +7 -4
  18. package/src/components/calendar/index.tsx +8 -0
  19. package/src/components/calendar/markup/index.tsx +7 -7
  20. package/src/components/carousel/index.tsx +8 -0
  21. package/src/components/carousel/markup/index.tsx +9 -0
  22. package/src/components/checkbox/index.tsx +7 -0
  23. package/src/components/chip/index.tsx +7 -1
  24. package/src/components/chip/markup/index.tsx +9 -0
  25. package/src/components/divider/index.tsx +4 -0
  26. package/src/components/divider/markup/Divider.tsx +11 -7
  27. package/src/components/divider/types/index.ts +1 -0
  28. package/src/components/divider/types/props.ts +27 -0
  29. package/src/components/drawer/index.tsx +7 -0
  30. package/src/components/drawer/markup/index.tsx +6 -0
  31. package/src/components/dropdown/index.tsx +7 -0
  32. package/src/components/dropdown/markup/Template.tsx +9 -2
  33. package/src/components/dropdown/markup/foundation/Container.tsx +30 -12
  34. package/src/components/dropdown/markup/index.tsx +9 -10
  35. package/src/components/dropdown/types/base.ts +13 -0
  36. package/src/components/dropdown/types/props.ts +19 -2
  37. package/src/components/form/index.tsx +7 -0
  38. package/src/components/form/markup/index.tsx +6 -2
  39. package/src/components/info-box/index.tsx +7 -0
  40. package/src/components/info-box/markup/InfoBox.tsx +1 -1
  41. package/src/components/info-box/markup/index.ts +6 -0
  42. package/src/components/info-box/types/props.ts +2 -2
  43. package/src/components/input/index.tsx +6 -1
  44. package/src/components/input/markup/foundation/Input.tsx +2 -2
  45. package/src/components/input/types/foundation.ts +1 -1
  46. package/src/components/navigation/index.tsx +7 -0
  47. package/src/components/navigation/markup/index.tsx +6 -0
  48. package/src/components/pagination/index.tsx +6 -1
  49. package/src/components/pagination/markup/index.tsx +7 -0
  50. package/src/components/pop-over/index.tsx +7 -0
  51. package/src/components/pop-over/markup/index.tsx +5 -4
  52. package/src/components/radio/index.tsx +5 -1
  53. package/src/components/scrollbar/hooks/index.ts +1 -1
  54. package/src/components/scrollbar/index.tsx +1 -1
  55. package/src/components/scrollbar/markup/index.tsx +1 -1
  56. package/src/components/scrollbar/types/index.ts +1 -1
  57. package/src/components/scrollbar/utils/index.ts +1 -1
  58. package/src/components/segmented-control/index.tsx +5 -1
  59. package/src/components/segmented-control/markup/index.ts +6 -0
  60. package/src/components/select/index.tsx +6 -1
  61. package/src/components/select/markup/Default.tsx +10 -13
  62. package/src/components/select/markup/foundation/Selected.tsx +31 -26
  63. package/src/components/select/markup/index.tsx +1 -1
  64. package/src/components/select/markup/multiple/Multiple.tsx +32 -15
  65. package/src/components/select/styles/select.scss +15 -6
  66. package/src/components/select/styles/variables.scss +4 -0
  67. package/src/components/select/types/multiple.ts +19 -0
  68. package/src/components/select/types/props.ts +19 -6
  69. package/src/components/select/utils/display.tsx +41 -0
  70. package/src/components/select/utils/index.ts +1 -4
  71. package/src/components/slot/index.tsx +7 -0
  72. package/src/components/slot/markup/index.tsx +6 -0
  73. package/src/components/spinner/hooks/index.ts +1 -1
  74. package/src/components/spinner/index.tsx +1 -1
  75. package/src/components/spinner/markup/index.tsx +1 -1
  76. package/src/components/spinner/types/index.ts +1 -1
  77. package/src/components/spinner/utils/index.ts +1 -1
  78. package/src/components/tab/index.tsx +5 -1
  79. package/src/components/tab/markup/index.tsx +8 -0
  80. package/src/components/table/index.tsx +3 -0
  81. package/src/components/tooltip/index.tsx +7 -0
  82. package/src/components/tooltip/markup/index.tsx +7 -6
@@ -11,27 +11,29 @@ import {
11
11
  } from "../../form/utils/form-field";
12
12
 
13
13
  /**
14
- * uds-foundation 토큰 위에서 block/layout/priority/slot API를 제공하는 기본 Button 컴포넌트.
14
+ * Button Component; Default action button
15
15
  * @component
16
16
  * @param {ButtonProps} props
17
- * @param {ElementType} [props.as="button"] 커스텀 요소(Renderer).
18
- * @param {React.ReactNode} [props.children] 문자열이면 `.button-label` span으로 감싼다.
19
- * @param {React.ReactNode} [props.left] 라벨 왼쪽 커스텀 슬롯.
20
- * @param {React.ReactNode} [props.right] 라벨 오른쪽 커스텀 슬롯.
21
- * @param {
22
- * "solid-xlarge" | "solid-large" | "solid-medium" | "solid-small" |
23
- * "outlined-xlarge" | "outlined-large" | "outlined-medium" | "outlined-small"
24
- * } [props.scale="solid-medium"] legacy spacing 식별자. fill/size가 없을 때만 fallback 용도로 사용한다.
25
- * @param {"solid" | "outlined"} [props.fill] 채움 스타일. scale과 별개로 직접 지정할 수 있다.
26
- * @param {"xlarge" | "large" | "medium" | "small" | "table"} [props.size] 버튼 높이/타이포 스케일. table은 셀 액션용 compact(24px) 규격이다.
27
- * @param {"primary" | "secondary" | "tertiary"} [props.priority="primary"] semantic color 세트.
28
- * @param {"default" | "readonly" | "disabled"} [props.state="default"] 내부 state. disabled prop과 조합된다.
29
- * @param {boolean} [props.block=false] true면 width 100%.
30
- * @param {boolean} [props.loading=false] true면 readonly 처리 + aria-busy.
17
+ * @param {ElementType} [props.as="button"] 렌더링할 요소.
18
+ * @param {React.ReactNode} [props.children] 문자열/숫자는 `.button-label`로 래핑하고, 그 외 ReactNode는 그대로 렌더링한다.
19
+ * @param {"solid-xlarge" | "solid-large" | "solid-medium" | "solid-small" | "outlined-xlarge" | "outlined-large" | "outlined-medium" | "outlined-small"} [props.scale] legacy fill/size fallback 식별자.
20
+ * @param {"solid" | "outlined"} props.fill 채움 스타일.
21
+ * @param {"xlarge" | "large" | "medium" | "small" | "table"} props.size 버튼 크기. `table`은 Table 셀 액션용 compact 규격이다.
22
+ * @param {"primary" | "secondary" | "tertiary"} props.priority semantic color priority.
23
+ * @param {"default" | "readonly" | "disabled"} [props.state="default"] 내부 상태. `disabled`/`loading`과 병합된다.
24
+ * @param {boolean} [props.block=false] true면 폭을 100%로 확장한다.
25
+ * @param {"full" | "fit" | "fill" | "auto" | number | string} [props.width] Form.Field width preset 또는 custom width.
26
+ * @param {boolean} [props.loading=false] true면 readonly 상태와 `aria-busy`를 강제한다.
27
+ * @param {React.ReactNode} [props.left] 라벨 왼쪽 콘텐츠.
28
+ * @param {React.ReactNode} [props.right] 라벨 오른쪽 콘텐츠.
31
29
  * @param {string} [props.className] 추가 className.
32
- * @param {"button" | "submit" | "reset"} [props.type="button"] button 요소일 때 type.
33
- * @param {boolean} [props.disabled] native disabled. state보다 우선한다.
34
- * @param {"hover" | "pressed"} [props.data-user-action] Story 시각 상태 고정용 data attr.
30
+ * @param {"button" | "submit" | "reset"} [props.type="button"] `button` 태그일native type.
31
+ * @param {boolean} [props.disabled] native disabled. `state`보다 우선한다.
32
+ * @param {"hover" | "pressed"} [props["data-user-action"]] Storybook 시각 상태 고정용 data attribute.
33
+ * @example
34
+ * <Button.Default fill="solid" size="medium" priority="primary">
35
+ * 확인
36
+ * </Button.Default>
35
37
  */
36
38
  const ButtonDefault = forwardRef<HTMLElement, ButtonProps>(
37
39
  (
@@ -4,12 +4,15 @@ import type { RoundButtonProps } from "../types";
4
4
  import { ButtonDefault } from "./Base";
5
5
 
6
6
  /**
7
- * 원형 버튼 템플릿. size만 받으면 적절한 scale을 선택하고 라운드 전용 className을 적용한다.
7
+ * Button Component; Rounded action button
8
8
  * @component
9
9
  * @param {RoundButtonProps} props
10
- * @param {"small" | "medium" | "large"} props.size size에 따라 scale을 매핑한다.
11
- * @param {"primary" | "secondary" | "tertiary"} props.priority semantic color.
12
- * @param {boolean} [props.block] width 확장 여부.
10
+ * @param {"small" | "medium" | "large"} props.size 라운드 버튼 크기 축.
11
+ * @param {"primary" | "secondary" | "tertiary"} props.priority semantic color priority.
12
+ * @example
13
+ * <Button.Rounded size="small" priority="primary" aria-label="추가">
14
+ * <span aria-hidden="true">+</span>
15
+ * </Button.Rounded>
13
16
  */
14
17
  const ButtonRounded = forwardRef<HTMLElement, RoundButtonProps>(
15
18
  // 라운드 전용 템플릿도 fill/size/priority를 직접 주입해 공통 규칙을 따른다.
@@ -4,12 +4,15 @@ import type { TextButtonProps } from "../types";
4
4
  import { ButtonDefault } from "./Base";
5
5
 
6
6
  /**
7
- * 텍스트 링크형 버튼 템플릿. size/priority 조합만 노출하고 나머지는 ButtonDefault에서 처리한다.
7
+ * Button Component; Text action button
8
8
  * @component
9
9
  * @param {TextButtonProps} props
10
- * @param {"small" | "medium" | "large"} props.size size에 따른 scale 파생.
11
- * @param {"secondary" | "tertiary"} props.priority semantic color.
12
- * @param {boolean} [props.block] 텍스트 버튼도 block 확장 가능.
10
+ * @param {"small" | "medium" | "large"} props.size 텍스트 버튼 크기 축.
11
+ * @param {"secondary" | "tertiary"} props.priority 텍스트 버튼 priority 축.
12
+ * @example
13
+ * <Button.Text size="medium" priority="secondary">
14
+ * 자세히 보기
15
+ * </Button.Text>
13
16
  */
14
17
  const ButtonText = forwardRef<HTMLElement, TextButtonProps>(
15
18
  // 템플릿 레벨에서 fill/size/priority를 강제해 ButtonDefault 정책과 동기화한다.
@@ -1,3 +1,11 @@
1
+ /**
2
+ * Calendar; date picker 조합 카테고리 배럴
3
+ * @desc
4
+ * - `Calendar.Root`: trigger + popover + core 조합 템플릿이다.
5
+ * - `Calendar.Container`: Header/Body/Footer 레이아웃 컨테이너다.
6
+ * - `Calendar.Core`: Mantine DatePicker SOT 렌더러다.
7
+ * - `CalendarHooks`, `CalendarUtils`: 관련 hook/util namespace다.
8
+ */
1
9
  import "./index.scss";
2
10
 
3
11
  export * from "./markup";
@@ -7,13 +7,13 @@ import CalendarRoot from "./Root";
7
7
  import { CalendarIcon } from "./Icon";
8
8
 
9
9
  /**
10
- * Calendar namespace.
11
- * - Root
12
- * - Container
13
- * - Header
14
- * - Body
15
- * - Footer
16
- * - Core
10
+ * Calendar; date picker namespace
11
+ * @desc
12
+ * - `Calendar.Root`: trigger + popover + core 조합 템플릿이다.
13
+ * - `Calendar.Container`: Header/Body/Footer 레이아웃 컨테이너다.
14
+ * - `Calendar.Header`, `Calendar.Body`, `Calendar.Footer`: depth 고정 레이아웃 섹션이다.
15
+ * - `Calendar.Core`: Mantine DatePicker SOT 렌더러다.
16
+ * - `Calendar.Icon`: 입력/네비게이션 아이콘 namespace다.
17
17
  */
18
18
  export const Calendar = {
19
19
  Root: CalendarRoot,
@@ -1,3 +1,11 @@
1
+ /**
2
+ * Carousel; viewport scroll carousel 카테고리 배럴
3
+ * @desc
4
+ * - `Carousel.Provider`: viewport/track 상태 공유 루트다.
5
+ * - `Carousel.Track`: viewport + track 렌더러다.
6
+ * - `Carousel.Control`, `Carousel.Button.*`: 이동 제어 도구다.
7
+ * - `useCarousel`: provider context 접근 hook이다.
8
+ */
1
9
  import "./index.scss";
2
10
 
3
11
  export * from "./markup";
@@ -6,6 +6,15 @@ import { CarouselPrevButton } from "./button/Prev";
6
6
  import { CarouselProvider } from "./Provider";
7
7
  import { CarouselTrack } from "./Track";
8
8
 
9
+ /**
10
+ * Carousel; viewport scroll carousel namespace
11
+ * @desc
12
+ * - `Carousel.Provider`: viewport/track 상태 공유 루트다.
13
+ * - `Carousel.Container`: Provider + Control 프리셋이다.
14
+ * - `Carousel.Control`: Prev/Track/Next 조합 컨트롤이다.
15
+ * - `Carousel.Track`: viewport + track 렌더러다.
16
+ * - `Carousel.Button.Base|Prev|Next`: 이동 제어 leaf 버튼이다.
17
+ */
9
18
  export const Carousel = {
10
19
  Provider: CarouselProvider,
11
20
  Container: CarouselContainer,
@@ -1,3 +1,10 @@
1
+ /**
2
+ * Checkbox; namespace 배럴 export
3
+ * @desc
4
+ * - `Checkbox`: leaf checkbox control
5
+ * - `CheckboxField`: label/helper 포함 field wrapper
6
+ * - hooks/types는 별도 배럴로 함께 export한다
7
+ */
1
8
  import "./index.scss";
2
9
 
3
10
  export * from "./markup";
@@ -1,5 +1,11 @@
1
1
  /**
2
- * chips 카테고리 배럴
2
+ * Chip; filter/assist/input chip 카테고리 배럴
3
+ * @desc
4
+ * - `Chip.Default`: style 분기 루트 컴포넌트다.
5
+ * - `Chip.ClickableStyle`: filter/assist button 렌더 전용 leaf다.
6
+ * - `Chip.InputStyle`: input figure 렌더 전용 leaf다.
7
+ * - `Chip.List`: chip 목록 템플릿이다.
8
+ * - `ChipProps`, `ChipListRootProps`, `ChipListItemData`: public contract 타입이다.
3
9
  */
4
10
  import "./index.scss";
5
11
 
@@ -4,6 +4,15 @@ import ChipInputStyle from "./InputStyle";
4
4
  import ChipLabel from "./Label";
5
5
  import { ChipListRoot } from "./ListRoot";
6
6
 
7
+ /**
8
+ * Chip; filter/assist/input namespace
9
+ * @desc
10
+ * - `Chip.Default`: style 분기 루트 컴포넌트다.
11
+ * - `Chip.ClickableStyle`: filter/assist button 렌더 전용 leaf다.
12
+ * - `Chip.InputStyle`: input figure 렌더 전용 leaf다.
13
+ * - `Chip.Label`: chip 텍스트 슬롯이다.
14
+ * - `Chip.List`: chip 목록 템플릿이다.
15
+ */
7
16
  export const Chip = {
8
17
  Default: ChipDefault,
9
18
  ClickableStyle: ChipClickableStyle,
@@ -1,5 +1,9 @@
1
1
  import "./index.scss";
2
2
 
3
+ /**
4
+ * Divider; 방향 전환형 구분선 엔트리
5
+ */
3
6
  import { Divider } from "./markup";
7
+ export type * from "./types";
4
8
 
5
9
  export { Divider };
@@ -1,11 +1,15 @@
1
1
  import clsx from "clsx";
2
+ import type { DividerProps } from "../types";
2
3
 
3
- export function Divider({
4
- className,
5
- direction = "vertical",
6
- }: {
7
- className?: string;
8
- direction?: "horizontal" | "vertical";
9
- }) {
4
+ /**
5
+ * Divider; 수평/수직 구분선 마크업
6
+ * @component
7
+ * @param {DividerProps} props
8
+ * @param {string} [props.className] divider 루트 className
9
+ * @param {"horizontal" | "vertical"} [props.direction] 구분선 방향
10
+ * @example
11
+ * <Divider direction="horizontal" />
12
+ */
13
+ export function Divider({ className, direction = "vertical" }: DividerProps) {
10
14
  return <div className={clsx("divider", direction, className)}></div>;
11
15
  }
@@ -0,0 +1 @@
1
+ export type { DividerDirection, DividerProps } from "./props";
@@ -0,0 +1,27 @@
1
+ import type { ComponentPropsWithoutRef } from "react";
2
+
3
+ /**
4
+ * DividerDirection; divider 방향 축
5
+ * @typedef {"horizontal" | "vertical"} DividerDirection
6
+ */
7
+ export type DividerDirection = "horizontal" | "vertical";
8
+
9
+ /**
10
+ * Divider Props; divider 루트 속성
11
+ * @property {string} [className] divider 루트 className
12
+ * @property {"horizontal" | "vertical"} [direction] 구분선 방향
13
+ * @see React.ComponentPropsWithoutRef<"div">
14
+ */
15
+ export interface DividerProps extends Omit<
16
+ ComponentPropsWithoutRef<"div">,
17
+ "children"
18
+ > {
19
+ /**
20
+ * divider 루트 className
21
+ */
22
+ className?: string;
23
+ /**
24
+ * 구분선 방향
25
+ */
26
+ direction?: DividerDirection;
27
+ }
@@ -1,3 +1,10 @@
1
+ /**
2
+ * Drawer; bottom sheet 카테고리 배럴
3
+ * @desc
4
+ * - `DrawerRoot`, `DrawerTrigger`, `DrawerPortal`, `DrawerOverlay`, `DrawerContent`: 루트/레이어 조합 축이다.
5
+ * - `DrawerHeader`, `DrawerBody`, `DrawerFooter`, `DrawerTitle`, `DrawerDescription`, `DrawerClose`: anatomy 도구다.
6
+ * - `useDrawerDrag`: handle drag 제어 hook이다.
7
+ */
1
8
  import "./index.scss";
2
9
 
3
10
  export * from "./markup";
@@ -1,3 +1,9 @@
1
1
  "use client";
2
2
 
3
+ /**
4
+ * Drawer; bottom sheet markup export 배럴
5
+ * @desc
6
+ * - `DrawerRoot`, `DrawerTrigger`, `DrawerPortal`, `DrawerOverlay`, `DrawerContent`: 루트/레이어 조합 축이다.
7
+ * - `DrawerHeader`, `DrawerBody`, `DrawerFooter`, `DrawerTitle`, `DrawerDescription`, `DrawerClose`: anatomy 도구다.
8
+ */
3
9
  export * from "./Drawer";
@@ -1,3 +1,10 @@
1
+ /**
2
+ * Dropdown; namespace 배럴 export
3
+ * @desc
4
+ * - `Dropdown.Root`, `Dropdown.Trigger`, `Dropdown.Container`: foundation 레이어
5
+ * - `Dropdown.Menu.*`: list/item 조합 레이어
6
+ * - `Dropdown.Template`: items[].selected + onChange(payload) preset
7
+ */
1
8
  import "./index.scss";
2
9
 
3
10
  export * from "./markup";
@@ -27,8 +27,9 @@ import {
27
27
  * @param {ReactNode} props.trigger trigger 요소
28
28
  * @param {DropdownTemplateItem[]} props.items 렌더링할 menu item 리스트
29
29
  * @param {(payload: DropdownTemplateChangePayload) => void} [props.onChange] 선택 결과 변경 콜백
30
- * @param {"small" | "medium" | "large"} [props.size="medium"] menu size scale
30
+ * @param {"xsmall" | "small" | "medium" | "large"} [props.size="medium"] menu size scale
31
31
  * @param {"match" | "fit-content" | "max-content" | string | number} [props.width="match"] panel width 옵션
32
+ * @param {"match" | "fit-content" | "max-content" | string | number} [props.minWidth] panel 최소 너비 옵션
32
33
  * @param {DropdownMenuProps} [props.rootProps] Dropdown.Root 전달 props
33
34
  * @param {DropdownContainerProps} [props.containerProps] Dropdown.Container 전달 props
34
35
  * @param {DropdownMenuListProps} [props.menuListProps] Dropdown.Menu.List 전달 props
@@ -40,6 +41,7 @@ const DropdownTemplate = ({
40
41
  onChange,
41
42
  size = "medium",
42
43
  width = "match",
44
+ minWidth,
43
45
  rootProps,
44
46
  containerProps,
45
47
  menuListProps,
@@ -199,7 +201,12 @@ const DropdownTemplate = ({
199
201
  return (
200
202
  <DropdownRoot {...rootProps}>
201
203
  <DropdownTrigger asChild>{trigger}</DropdownTrigger>
202
- <DropdownContainer {...containerProps} size={size} width={width}>
204
+ <DropdownContainer
205
+ {...containerProps}
206
+ size={size}
207
+ width={width}
208
+ minWidth={minWidth}
209
+ >
203
210
  <DropdownMenuList {...menuListProps}>
204
211
  {items.length > 0 ? (
205
212
  <>
@@ -15,6 +15,7 @@ import { useDropdownContext } from "./Provider";
15
15
  * @param {string} [props.className] panel className
16
16
  * @param {DropdownSize} [props.size="medium"] option height scale
17
17
  * @param {DropdownPanelWidth} [props.width="match"] panel width 옵션
18
+ * @param {DropdownPanelMinWidth} [props.minWidth] panel 최소 너비 옵션
18
19
  * @param {HTMLElement | null} [props.portalContainer] portal 컨테이너
19
20
  * @param {"start" | "center" | "end"} [props.align="start"] 정렬 기준
20
21
  * @param {"top" | "right" | "bottom" | "left"} [props.side="bottom"] 패널 위치
@@ -33,6 +34,7 @@ const DropdownContainer = forwardRef<HTMLDivElement, DropdownContainerProps>(
33
34
  className,
34
35
  size = "medium",
35
36
  width = "match",
37
+ minWidth,
36
38
  portalContainer,
37
39
  align = "start",
38
40
  side = "bottom",
@@ -47,29 +49,46 @@ const DropdownContainer = forwardRef<HTMLDivElement, DropdownContainerProps>(
47
49
  const [panelWidth, setPanelWidth] = useState<number>();
48
50
  const shouldMatchTriggerWidth = width === "match";
49
51
 
50
- const resolvedMinWidth = useMemo(() => {
51
- if (shouldMatchTriggerWidth) {
52
+ const resolvePanelSizeValue = (
53
+ value?: string | number,
54
+ fallbackToMatch?: boolean,
55
+ ): string | undefined => {
56
+ if (value === undefined) {
52
57
  return undefined;
53
58
  }
54
59
 
55
- if (typeof width === "number") {
56
- return `${width}px`;
60
+ if (typeof value === "number") {
61
+ return `${value}px`;
57
62
  }
58
63
 
59
- if (typeof width === "string") {
60
- if (width === "fit-content") {
64
+ if (typeof value === "string") {
65
+ if (value === "fit-content") {
61
66
  return "fit-content";
62
67
  }
63
- if (width === "max-content") {
68
+ if (value === "max-content") {
64
69
  return "max-content";
65
70
  }
66
- if (width.trim().length > 0 && width !== "match") {
67
- return width;
71
+ if (fallbackToMatch && value === "match" && panelWidth) {
72
+ return `${panelWidth}px`;
73
+ }
74
+ if (value.trim().length > 0 && value !== "match") {
75
+ return value;
68
76
  }
69
77
  }
70
78
 
71
79
  return undefined;
72
- }, [shouldMatchTriggerWidth, width]);
80
+ };
81
+ const resolvedWidth = useMemo(() => {
82
+ if (shouldMatchTriggerWidth) {
83
+ return panelWidth ? `${panelWidth}px` : undefined;
84
+ }
85
+
86
+ return resolvePanelSizeValue(width);
87
+ }, [panelWidth, shouldMatchTriggerWidth, width]);
88
+ const resolvedMinWidth = useMemo(() => {
89
+ // 변경 설명: width 계약은 기준 폭, minWidth 계약은 최소 보장 폭으로 분리한다.
90
+ return resolvePanelSizeValue(minWidth, true);
91
+ }, [minWidth, panelWidth]);
73
92
 
74
93
  useEffect(() => {
75
94
  if (!shouldMatchTriggerWidth) {
@@ -110,8 +129,7 @@ const DropdownContainer = forwardRef<HTMLDivElement, DropdownContainerProps>(
110
129
  className={clsx("dropdown-panel", `dropdown-panel-${size}`, className)}
111
130
  style={{
112
131
  ...style,
113
- width:
114
- shouldMatchTriggerWidth && panelWidth ? panelWidth : style?.width,
132
+ width: resolvedWidth ?? style?.width,
115
133
  minWidth:
116
134
  resolvedMinWidth !== undefined ? resolvedMinWidth : style?.minWidth,
117
135
  }}
@@ -2,16 +2,15 @@ import { DropdownFoundation } from "./foundation";
2
2
  import DropdownTemplate from "./Template";
3
3
 
4
4
  /**
5
- * Dropdown
6
- * - Provider
7
- * - Root
8
- * - Container
9
- * - Menu
10
- * - Item
11
- * - List
12
- * - Panel
13
- * - Trigger
14
- * - Template
5
+ * Dropdown; 컴포넌트 모듈
6
+ * @namespace Dropdown
7
+ * - `Dropdown.Provider`
8
+ * - `Dropdown.Root`
9
+ * - `Dropdown.Trigger`
10
+ * - `Dropdown.Container`
11
+ * - `Dropdown.Menu.Item`
12
+ * - `Dropdown.Menu.List`
13
+ * - `Dropdown.Template`
15
14
  */
16
15
  export const Dropdown = {
17
16
  ...DropdownFoundation,
@@ -16,3 +16,16 @@ export type DropdownPanelWidth =
16
16
  | "max-content"
17
17
  | string
18
18
  | number;
19
+
20
+ /**
21
+ * Dropdown panel min-width 옵션
22
+ * - "match": trigger width를 최소 폭으로 보장
23
+ * - "fit-content" | "max-content": 콘텐츠 기준 최소 폭
24
+ * - string/number: 커스텀 최소 폭
25
+ */
26
+ export type DropdownPanelMinWidth =
27
+ | "match"
28
+ | "fit-content"
29
+ | "max-content"
30
+ | string
31
+ | number;
@@ -6,7 +6,11 @@ import type {
6
6
  import type { HTMLAttributes, ReactNode, RefObject } from "react";
7
7
 
8
8
  import type { CheckboxProps } from "../../checkbox/types";
9
- import type { DropdownPanelWidth, DropdownSize } from "./base";
9
+ import type {
10
+ DropdownPanelMinWidth,
11
+ DropdownPanelWidth,
12
+ DropdownSize,
13
+ } from "./base";
10
14
 
11
15
  /**
12
16
  * Dropdown template value 타입
@@ -17,6 +21,7 @@ export type DropdownTemplateValue = string | number;
17
21
  * Dropdown Container props
18
22
  * @property {DropdownSize} [size="medium"] option 높이 스케일
19
23
  * @property {DropdownPanelWidth} [width="match"] dropdown panel width 옵션
24
+ * @property {DropdownPanelMinWidth} [minWidth] dropdown panel 최소 너비 옵션
20
25
  * @property {HTMLElement | null} [portalContainer] portal을 렌더링할 DOM 컨테이너
21
26
  */
22
27
  export interface DropdownContainerProps extends DropdownMenuContentProps {
@@ -28,6 +33,10 @@ export interface DropdownContainerProps extends DropdownMenuContentProps {
28
33
  * trigger 너비에 맞춰 dropdown 너비를 맞출지 여부
29
34
  */
30
35
  width?: DropdownPanelWidth;
36
+ /**
37
+ * dropdown 최소 너비 제약
38
+ */
39
+ minWidth?: DropdownPanelMinWidth;
31
40
  /**
32
41
  * portal을 렌더링할 DOM 컨테이너
33
42
  */
@@ -191,6 +200,7 @@ export interface DropdownTemplateChangePayload {
191
200
  * @property {(payload: DropdownTemplateChangePayload) => void} [onChange] 선택 결과 변경 콜백
192
201
  * @property {DropdownSize} [size="medium"] surface height scale
193
202
  * @property {DropdownPanelWidth} [width="match"] panel width 옵션
203
+ * @property {DropdownPanelMinWidth} [minWidth] panel 최소 너비 옵션
194
204
  * @property {DropdownMenuProps} [rootProps] Root 에 전달할 props
195
205
  * @property {DropdownContainerProps} [containerProps] Container 에 전달할 props
196
206
  * @property {DropdownMenuListProps} [menuListProps] MenuList 에 전달할 props
@@ -205,6 +215,10 @@ export interface DropdownTemplateProps {
205
215
  onChange?: (payload: DropdownTemplateChangePayload) => void;
206
216
  size?: DropdownSize;
207
217
  width?: DropdownPanelWidth;
218
+ /**
219
+ * panel 최소 너비 옵션
220
+ */
221
+ minWidth?: DropdownPanelMinWidth;
208
222
  /**
209
223
  * Root 에 전달할 props
210
224
  * - 타입 출처를 명확히 하기 위해 Radix 원본 타입을 직접 사용한다.
@@ -213,7 +227,10 @@ export interface DropdownTemplateProps {
213
227
  /**
214
228
  * Container 에 전달할 props
215
229
  */
216
- containerProps?: Omit<DropdownContainerProps, "children" | "size" | "width">;
230
+ containerProps?: Omit<
231
+ DropdownContainerProps,
232
+ "children" | "size" | "width" | "minWidth"
233
+ >;
217
234
  /**
218
235
  * MenuList 에 전달할 props
219
236
  */
@@ -1,3 +1,10 @@
1
+ /**
2
+ * Form; provider + field layout 카테고리 배럴
3
+ * @desc
4
+ * - `Form.Provider`: react-hook-form provider 래퍼다.
5
+ * - `Form.Field.*`: Container/Header/Body/Footer/Template 레이아웃 도구다.
6
+ * - `getFormFieldWidthAttr`, `getFormFieldWidthValue`: width 계산 util이다.
7
+ */
1
8
  import "./index.scss";
2
9
 
3
10
  export { Form } from "./markup";
@@ -2,8 +2,12 @@ import FormProvider from "./Provider";
2
2
  import { FormField } from "./form-field";
3
3
 
4
4
  /**
5
- * Form namespace 조립 전용 파일.
6
- * - index.tsx 배럴 파일에서 로직/객체 생성을 분리한다.
5
+ * Form; provider + field layout namespace
6
+ * @desc
7
+ * - `Form.Provider`: react-hook-form provider 래퍼다.
8
+ * - `Form.Field.Container`: field 루트 wrapper다.
9
+ * - `Form.Field.Header`, `Form.Field.Body`, `Form.Field.Footer`: depth 고정 섹션이다.
10
+ * - `Form.Field.Template`: 반복 조합용 필드 템플릿이다.
7
11
  */
8
12
  export const Form = {
9
13
  Provider: FormProvider,
@@ -1,3 +1,10 @@
1
+ /**
2
+ * InfoBox; 안내/상태 메시지 카테고리 배럴
3
+ * @desc
4
+ * - `InfoBox`: heading/body를 렌더링하는 루트 컴포넌트다.
5
+ * - `InfoBoxIcon`: state별 아이콘 맵이다.
6
+ * - `InfoBoxProps`, `InfoBoxState`: public contract 타입이다.
7
+ */
1
8
  import "./index.scss";
2
9
 
3
10
  export * from "./markup";
@@ -7,7 +7,7 @@ import { InfoBoxIcon } from "./Icon";
7
7
  * InfoBox Component; 안내 메시지 전용 정보 박스
8
8
  * @component
9
9
  * @param {InfoBoxProps} props
10
- * @param {"info"} [props.state="info"] 현재는 info 상태만 지원한다.
10
+ * @param {"info" | "caution" | "success" | "error"} [props.state="info"] 안내/상태 메시지 시각 축이다.
11
11
  * @param {React.ReactNode} [props.heading] 상단 제목 영역.
12
12
  * @param {React.ReactNode} [props.children] 본문 콘텐츠.
13
13
  * @param {string} [props.className] 루트 section className.
@@ -1,2 +1,8 @@
1
+ /**
2
+ * InfoBox; 안내/상태 메시지 markup export 배럴
3
+ * @desc
4
+ * - `InfoBox`: heading/body를 렌더링하는 루트 컴포넌트다.
5
+ * - `InfoBoxIcon`: state별 아이콘 맵이다.
6
+ */
1
7
  export { default as InfoBox } from "./InfoBox";
2
8
  export * from "./Icon";
@@ -7,7 +7,7 @@ type NativeSectionProps = Omit<ComponentPropsWithoutRef<"section">, "title">;
7
7
 
8
8
  /**
9
9
  * InfoBox Props; info-box 루트 속성
10
- * @property {"info" | "caution" | "success" | "error"} [state] state 값. 현재는 info만 허용한다.
10
+ * @property {"info" | "caution" | "success" | "error"} [state] info/caution/success/error 상태 축이다.
11
11
  * @property {ReactNode} [heading] 상단 제목 콘텐츠
12
12
  * @property {ReactNode} [children] 본문 콘텐츠
13
13
  * @property {string} [className] 루트 section className
@@ -15,7 +15,7 @@ type NativeSectionProps = Omit<ComponentPropsWithoutRef<"section">, "title">;
15
15
  */
16
16
  export interface InfoBoxProps extends Omit<NativeSectionProps, "children"> {
17
17
  /**
18
- * state 값. 현재는 info만 허용한다.
18
+ * info/caution/success/error 상태 축이다.
19
19
  * - info, caution, success, error
20
20
  */
21
21
  state?: InfoBoxState;
@@ -1,5 +1,10 @@
1
1
  /**
2
- * input 카테고리 배럴 placeholder: 실제 구현은 markup/ 하위에 추가한다.
2
+ * Input; namespace 배럴 export
3
+ * @desc
4
+ * - `Input.Base`, `Input.TextArea`: foundation control
5
+ * - `Input.Text.*`: text scenario wrappers
6
+ * - `Input.Date`, `Input.Address`, `Input.File`: category presets
7
+ * - hooks/utils/types는 별도 배럴로 함께 export한다
3
8
  */
4
9
  import "./index.scss";
5
10
 
@@ -34,7 +34,7 @@ import InputBaseUtil from "./Utility";
34
34
  * @param {"default" | "active" | "focused" | "success" | "error" | "disabled" | "loading"} [props.state="default"] 시각 상태(tertiary는 disabled 외 feedback color 미적용)
35
35
  * @param {boolean} [props.block=false] true면 width 100% (`priority="table"`은 width 미지정 시 full 기본)
36
36
  * @param {React.ReactNode} [props.inlineLabel] tertiary border 내부 상단 라벨
37
- * @param {FormFieldWidth} [props.width] width preset
37
+ * @param {"full" | "fit" | "fill" | "auto" | number | string} [props.width] width preset 또는 custom width
38
38
  * @param {React.ReactNode} [props.left] 입력 왼쪽 슬롯(아이콘/텍스트)
39
39
  * @param {React.ReactNode} [props.right] 입력 오른쪽 슬롯
40
40
  * @param {React.ReactNode} [props.clear] 입력값 초기화 아이콘. 지정하지 않으면 기본 Reset 아이콘
@@ -54,7 +54,7 @@ import InputBaseUtil from "./Utility";
54
54
  * @param {string | number | readonly string[]} [props.value] 제어형 값
55
55
  * @param {string | number | readonly string[]} [props.defaultValue] 비제어 초기값
56
56
  * @param {string} [props.type="text"] native input type
57
- * @param {boolean} [props.readOnly] native readOnly
57
+ * @param {boolean} [props.readOnly] native readOnly. value가 없으면 placeholder는 alternative 토큰으로 그대로 노출된다.
58
58
  */
59
59
  const InputBase = forwardRef<HTMLInputElement, InputProps>(
60
60
  (