@pylerai/design-system 2.4.0 → 2.5.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 (43) hide show
  1. package/README.md +2 -1
  2. package/dist/components/ui/badge.d.ts +3 -0
  3. package/dist/components/ui/badge.js +28 -20
  4. package/dist/components/ui/button.d.ts +1 -1
  5. package/dist/components/ui/button.js +38 -25
  6. package/dist/components/ui/dropdown.js +89 -86
  7. package/dist/components/ui/input.js +30 -30
  8. package/dist/components/ui/label.d.ts +1 -1
  9. package/dist/components/ui/label.js +11 -10
  10. package/dist/components/ui/media-slider.d.ts +20 -0
  11. package/dist/components/ui/media-slider.js +90 -0
  12. package/dist/components/ui/progress-bar.d.ts +21 -0
  13. package/dist/components/ui/progress-bar.js +45 -0
  14. package/dist/components/ui/search-field.js +45 -43
  15. package/dist/components/ui/table.js +33 -33
  16. package/dist/components/ui/tabs.js +24 -24
  17. package/dist/components/ui/timeline-tracks.d.ts +66 -0
  18. package/dist/components/ui/timeline-tracks.js +80 -0
  19. package/dist/components/ui/timeline.d.ts +31 -0
  20. package/dist/components/ui/timeline.js +119 -0
  21. package/dist/components/ui/toast.js +5 -5
  22. package/dist/components/ui/video-player.d.ts +29 -0
  23. package/dist/components/ui/video-player.js +174 -0
  24. package/dist/index.d.ts +7 -1
  25. package/dist/index.js +38 -32
  26. package/dist/lib/field-box.d.ts +13 -0
  27. package/dist/lib/field-box.js +11 -0
  28. package/dist/lib/format-time.d.ts +5 -0
  29. package/dist/lib/format-time.js +8 -0
  30. package/dist/lib/icons.d.ts +6 -0
  31. package/dist/lib/icons.js +68 -44
  32. package/dist/lib/utils.d.ts +1 -0
  33. package/dist/lib/utils.js +14 -7
  34. package/dist/style.css +1 -1
  35. package/dist/tokens/pyler-light.d.ts +12 -0
  36. package/dist/tokens/pyler-light.js +27 -23
  37. package/dist/tokens/shadow-radius.d.ts +3 -3
  38. package/dist/tokens/shadow-radius.js +3 -3
  39. package/dist/tokens/typography.d.ts +8 -2
  40. package/dist/tokens/typography.js +6 -0
  41. package/package.json +1 -1
  42. package/pyler-tailwind-colors.cjs +4 -0
  43. package/pyler-typography.cjs +7 -0
package/README.md CHANGED
@@ -207,6 +207,7 @@ flatPylerLightTokens.forEach(({ path, hex, cssVar }) => { /* 문서화·순회
207
207
  | Body 4 M | 12px | 500 | `text-body-4-m` | `.ds-type-body-4-m` |
208
208
  | Body 4 R | 12px | 400 | `text-body-4-r` | `.ds-type-body-4-r` |
209
209
  | Label 1 R | 11px | 400 | `text-label-1-r` | `.ds-type-label-1-r` |
210
+ | Label 1 SB | 11px | 600 | `text-label-1-sb` | `.ds-type-label-1-sb` |
210
211
 
211
212
  폰트는 Pretendard JP(`--ds-font-sans`)를 사용합니다. `@pylerai/design-system/fonts.css`를 함께 로드하면 등록됩니다(위 "폰트" 절 참고).
212
213
 
@@ -274,7 +275,7 @@ Dim은 `var(--ds-dim-default)`로 제공됩니다. Storybook **Foundation → Ra
274
275
  - `Checkbox`·`Radio`는 자체적으로 `<label>`을 렌더합니다. **밖에서 `<label>`로 감싸지 마세요** — label이 중첩되어 연결이 깨집니다. 텍스트는 children으로 넘기고(`<Checkbox>동의</Checkbox>`), 텍스트가 없으면 `aria-label`을 지정하세요.
275
276
  - `Toggle`, 아이콘 전용 `Button`, 라벨 없는 `Dropdown`/`SearchField`에는 `aria-label`이 필요합니다.
276
277
 
277
- > ⚠️ **컬러 대비는 아직 WCAG AA를 충족하지 못합니다.** `--ds-text-normal`(#969696)은 흰 배경에서 2.95:1, `--ds-text-disabled`(#cccccc)는 1.6:1, 흰 글자 + `--ds-primary-blue-normal`(#2c80f5)은 3.8:1로 기준(4.5:1)에 미달합니다. 토큰을 바꾸면 제품 전체 색이 바뀌므로 디자이너 결정이 필요한 사안이며, 그때까지 axe의 `color-contrast` 규칙만 꺼 둔 상태입니다(`.storybook/preview.ts`).
278
+ > ⚠️ **컬러 대비는 아직 WCAG AA를 충족하지 못합니다.** `--ds-text-normal`(#afafaf, 이전 #969696)은 흰 배경에서 2.2:1, `--ds-text-disabled`(#cccccc)는 1.6:1, 흰 글자 + `--ds-primary-blue-normal`(#2c80f5)은 3.8:1로 기준(4.5:1)에 미달합니다. 토큰을 바꾸면 제품 전체 색이 바뀌므로 디자이너 결정이 필요한 사안이며, 그때까지 axe의 `color-contrast` 규칙만 꺼 둔 상태입니다(`.storybook/preview.ts`).
278
279
 
279
280
  ## 📁 프로젝트 구조
280
281
 
@@ -2,9 +2,12 @@ import { DsSize } from '../../lib/size';
2
2
  import * as React from "react";
3
3
  export type BadgeColor = "normal" | "neutral" | "negative" | "positive";
4
4
  export type BadgeSize = Extract<DsSize, "md" | "sm">;
5
+ /** `outline` 흰 바탕 + 테두리(기본) · `surface` 테두리 없이 연한 면(Figma 2026-11 추가) */
6
+ export type BadgeVariant = "outline" | "surface";
5
7
  export interface BadgeProps extends React.HTMLAttributes<HTMLSpanElement> {
6
8
  color?: BadgeColor;
7
9
  size?: BadgeSize;
10
+ variant?: BadgeVariant;
8
11
  }
9
12
  declare const Badge: React.ForwardRefExoticComponent<BadgeProps & React.RefAttributes<HTMLSpanElement>>;
10
13
  export { Badge };
@@ -1,8 +1,13 @@
1
1
  "use client";
2
- import { jsx as x } from "react/jsx-runtime";
3
- import { cn as i } from "../../lib/utils.js";
4
- import * as l from "react";
5
- const b = {
2
+ import { jsx as l } from "react/jsx-runtime";
3
+ import { cn as b } from "../../lib/utils.js";
4
+ import * as c from "react";
5
+ const g = {
6
+ normal: "bg-ds-primary-blue-muted",
7
+ neutral: "bg-ds-surface-normal",
8
+ negative: "bg-ds-primary-red-muted",
9
+ positive: "bg-ds-primary-green-muted"
10
+ }, u = {
6
11
  normal: {
7
12
  border: "border-ds-primary-blue-normal",
8
13
  text: "text-ds-primary-blue-normal"
@@ -19,31 +24,34 @@ const b = {
19
24
  border: "border-ds-primary-green-normal",
20
25
  text: "text-ds-primary-green-normal"
21
26
  }
22
- }, c = {
27
+ }, f = {
23
28
  md: { padding: "px-2 pt-[5px] pb-[3px]", text: "text-body-3-m" },
24
29
  sm: { padding: "px-1 pt-[3px] pb-[1px]", text: "text-label-1-r" }
25
- }, g = l.forwardRef(
26
- ({ color: r = "normal", size: e = "md", className: t, children: o, ...s }, d) => {
27
- const { border: a, text: n } = b[r], { padding: p, text: m } = c[e];
28
- return /* @__PURE__ */ x(
30
+ }, y = {
31
+ md: { padding: "px-2 pt-[5px] pb-[3px]", text: "text-body-3-m" },
32
+ sm: { padding: "px-1.5 pt-[5px] pb-[3px]", text: "text-label-1-sb" }
33
+ }, C = c.forwardRef(
34
+ ({ color: e = "normal", size: t = "md", variant: s = "outline", className: a, children: d, ...o }, n) => {
35
+ const r = s === "surface", { border: p, text: m } = u[e], { padding: i, text: x } = (r ? y : f)[t];
36
+ return /* @__PURE__ */ l(
29
37
  "span",
30
38
  {
31
- ref: d,
32
- className: i(
33
- "inline-flex items-center justify-center border rounded-ds-ss bg-ds-background-default whitespace-nowrap",
34
- a,
35
- p,
36
- n,
39
+ ref: n,
40
+ className: b(
41
+ "inline-flex items-center justify-center rounded-ds-ss whitespace-nowrap",
42
+ r ? g[e] : ["border bg-ds-background-default", p],
43
+ i,
37
44
  m,
38
- t
45
+ x,
46
+ a
39
47
  ),
40
- ...s,
41
- children: o
48
+ ...o,
49
+ children: d
42
50
  }
43
51
  );
44
52
  }
45
53
  );
46
- g.displayName = "Badge";
54
+ C.displayName = "Badge";
47
55
  export {
48
- g as Badge
56
+ C as Badge
49
57
  };
@@ -3,7 +3,7 @@ import { VariantProps } from 'class-variance-authority';
3
3
  import * as React from "react";
4
4
  declare const buttonVariants: (props?: ({
5
5
  variant?: "text" | "icon" | "filled" | "outlined" | "indicator" | null | undefined;
6
- color?: "blue" | "red" | "green" | null | undefined;
6
+ color?: "blue" | "red" | "green" | "neutral" | null | undefined;
7
7
  size?: "sm" | "md" | "lg" | "xl" | null | undefined;
8
8
  } & import('class-variance-authority/types').ClassProp) | undefined) => string;
9
9
  export interface ButtonProps extends Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, "color">, VariantProps<typeof buttonVariants> {
@@ -1,9 +1,9 @@
1
1
  "use client";
2
- import { jsx as a, jsxs as O, Fragment as k } from "react/jsx-runtime";
2
+ import { jsx as s, jsxs as O, Fragment as k } from "react/jsx-runtime";
3
3
  import { cn as m } from "../../lib/utils.js";
4
4
  import { Slot as B } from "@radix-ui/react-slot";
5
5
  import { cva as R } from "class-variance-authority";
6
- import * as g from "react";
6
+ import * as p from "react";
7
7
  const v = R(
8
8
  "inline-flex items-center justify-center whitespace-nowrap font-sans transition-colors duration-150 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 disabled:pointer-events-none select-none",
9
9
  {
@@ -12,13 +12,16 @@ const v = R(
12
12
  filled: "rounded-ds-md",
13
13
  outlined: "rounded-ds-md border border-ds-line-normal bg-ds-background-default text-ds-text-strong hover:bg-ds-surface-normal active:bg-ds-surface-strong disabled:bg-ds-surface-normal disabled:text-ds-text-disabled",
14
14
  text: "rounded-ds-md bg-transparent text-ds-text-strong disabled:text-ds-text-disabled",
15
- icon: "rounded-ds-md",
15
+ // radius 는 size compound 에서 — buttonVariants 를 cn 없이 직접 쓰는 앱에서도 sm 4px 이 나오도록
16
+ icon: "",
16
17
  indicator: "rounded-full inline-block"
17
18
  },
18
19
  color: {
19
20
  blue: "",
20
21
  red: "",
21
- green: ""
22
+ green: "",
23
+ /** 회색 버튼 — Figma 2026-11 추가. filled·icon 에서 쓴다(indicator 는 대상 아님) */
24
+ neutral: ""
22
25
  },
23
26
  size: {
24
27
  sm: "h-7 gap-1",
@@ -44,6 +47,11 @@ const v = R(
44
47
  color: "green",
45
48
  className: "bg-ds-primary-green-normal text-white hover:bg-ds-primary-green-hovered active:bg-ds-primary-green-hovered disabled:bg-ds-primary-green-disabled disabled:text-white focus-visible:ring-ds-primary-green-normal"
46
49
  },
50
+ {
51
+ variant: "filled",
52
+ color: "neutral",
53
+ className: "bg-ds-surface-strong text-ds-text-strong hover:bg-ds-surface-hovered active:bg-ds-surface-hovered disabled:bg-ds-surface-disabled disabled:text-ds-text-disabled focus-visible:ring-ds-text-strong"
54
+ },
47
55
  // ── icon filled × color ───────────────────────────────────────────────
48
56
  {
49
57
  variant: "icon",
@@ -60,17 +68,22 @@ const v = R(
60
68
  color: "green",
61
69
  className: "bg-ds-primary-green-normal text-white hover:bg-ds-primary-green-hovered active:bg-ds-primary-green-hovered disabled:bg-ds-primary-green-disabled disabled:text-white focus-visible:ring-ds-primary-green-normal"
62
70
  },
71
+ {
72
+ variant: "icon",
73
+ color: "neutral",
74
+ className: "bg-ds-surface-strong text-ds-text-strong hover:bg-ds-surface-hovered active:bg-ds-surface-hovered disabled:bg-ds-surface-disabled disabled:text-ds-text-disabled focus-visible:ring-ds-text-strong"
75
+ },
63
76
  // ── icon × size (fixed square dimensions + SVG sizing) ──────────────
64
- { variant: "icon", size: "sm", className: "h-7 w-7 p-0 rounded [&_svg]:size-3" },
65
- { variant: "icon", size: "md", className: "h-8 w-8 p-0 [&_svg]:size-5" },
66
- { variant: "icon", size: "lg", className: "h-9 w-9 p-0 [&_svg]:size-5" },
67
- { variant: "icon", size: "xl", className: "h-11 w-11 p-0 [&_svg]:size-7" },
77
+ { variant: "icon", size: "sm", className: "h-7 w-7 p-0 rounded-ds-ss [&_svg]:size-3" },
78
+ { variant: "icon", size: "md", className: "h-8 w-8 p-0 rounded-ds-md [&_svg]:size-5" },
79
+ { variant: "icon", size: "lg", className: "h-9 w-9 p-0 rounded-ds-md [&_svg]:size-5" },
80
+ { variant: "icon", size: "xl", className: "h-11 w-11 p-0 rounded-ds-md [&_svg]:size-7" },
68
81
  // ── filled/outlined/text size → padding + typography ─────────────────
69
- { variant: "filled", size: "sm", className: "px-2 text-body-4-m rounded" },
82
+ { variant: "filled", size: "sm", className: "px-2 text-body-4-m" },
70
83
  { variant: "filled", size: "md", className: "px-3 text-body-3-m" },
71
84
  { variant: "filled", size: "lg", className: "px-3.5 text-body-2-m" },
72
85
  { variant: "filled", size: "xl", className: "px-[18px] text-body-2-m" },
73
- { variant: "outlined", size: "sm", className: "px-2 text-body-4-m rounded" },
86
+ { variant: "outlined", size: "sm", className: "px-2 text-body-4-m" },
74
87
  { variant: "outlined", size: "md", className: "px-3 text-body-3-m" },
75
88
  { variant: "outlined", size: "lg", className: "px-3.5 text-body-2-m" },
76
89
  { variant: "outlined", size: "xl", className: "px-[18px] text-body-2-m" },
@@ -107,9 +120,9 @@ const v = R(
107
120
  size: "md"
108
121
  }
109
122
  }
110
- ), _ = g.forwardRef(
123
+ ), _ = p.forwardRef(
111
124
  ({
112
- className: s,
125
+ className: a,
113
126
  variant: e,
114
127
  color: i,
115
128
  size: r,
@@ -120,44 +133,44 @@ const v = R(
120
133
  rightIcon: t,
121
134
  children: b,
122
135
  ...h
123
- }, y) => {
124
- const f = c ? B : "button", N = e === "icon" && x === "outlined" ? "bg-ds-background-default border border-ds-line-normal text-ds-text-strong hover:bg-ds-surface-normal active:bg-ds-surface-strong disabled:text-ds-text-disabled" : void 0, l = !!d, p = !!t, z = e !== "icon" && e !== "indicator" && (l || p) ? (() => {
125
- const n = r ?? "md", o = {
136
+ }, f) => {
137
+ const y = c ? B : "button", N = e === "icon" && x === "outlined" ? "bg-ds-background-default border border-ds-line-normal text-ds-text-strong hover:bg-ds-surface-normal active:bg-ds-surface-strong disabled:text-ds-text-disabled" : void 0, l = !!d, g = !!t, z = e !== "icon" && e !== "indicator" && (l || g) ? (() => {
138
+ const o = r ?? "md", n = {
126
139
  sm: { leftOnly: "pl-1 pr-2", rightOnly: "pl-2 pr-1", both: "px-1" },
127
140
  md: { leftOnly: "pl-2 pr-3", rightOnly: "pl-3 pr-2", both: "px-2" },
128
141
  lg: { leftOnly: "pl-2.5 pr-3.5", rightOnly: "pl-3.5 pr-2.5", both: "px-2.5" },
129
142
  xl: { leftOnly: "pl-3.5 pr-[18px]", rightOnly: "pl-[18px] pr-3.5", both: "px-3.5" }
130
- }, w = l && p ? o[n].both : l ? o[n].leftOnly : o[n].rightOnly;
143
+ }, w = l && g ? n[o].both : l ? n[o].leftOnly : n[o].rightOnly;
131
144
  return m(w, "gap-1");
132
145
  })() : void 0;
133
- return /* @__PURE__ */ a(
134
- f,
146
+ return /* @__PURE__ */ s(
147
+ y,
135
148
  {
136
149
  className: m(
137
150
  v({ variant: e, color: i, size: r }),
138
151
  e === "icon" && u === "round" && "rounded-full",
139
152
  N,
140
153
  z,
141
- s
154
+ a
142
155
  ),
143
- ref: y,
156
+ ref: f,
144
157
  ...h,
145
158
  children: c ? b : /* @__PURE__ */ O(k, { children: [
146
- d && /* @__PURE__ */ a("span", { className: "shrink-0", children: d }),
159
+ d && /* @__PURE__ */ s("span", { className: "shrink-0", children: d }),
147
160
  b,
148
- t && /* @__PURE__ */ a("span", { className: "shrink-0", children: t })
161
+ t && /* @__PURE__ */ s("span", { className: "shrink-0", children: t })
149
162
  ] })
150
163
  }
151
164
  );
152
165
  }
153
166
  );
154
167
  _.displayName = "Button";
155
- const j = g.forwardRef(
156
- ({ color: s = "blue", size: e = "md", className: i }, r) => /* @__PURE__ */ a(
168
+ const j = p.forwardRef(
169
+ ({ color: a = "blue", size: e = "md", className: i }, r) => /* @__PURE__ */ s(
157
170
  "span",
158
171
  {
159
172
  className: m(
160
- v({ variant: "indicator", color: s, size: e }),
173
+ v({ variant: "indicator", color: a, size: e }),
161
174
  i
162
175
  ),
163
176
  ref: r
@@ -1,7 +1,7 @@
1
1
  "use client";
2
- import { jsxs as u, jsx as t } from "react/jsx-runtime";
3
- import { DropdownArrowIcon as V, SelectCheckIcon as ee, CheckboxCheckedIcon as re, CheckboxUncheckedIcon as te, RadioCheckedIcon as ne, RadioUncheckedIcon as se } from "../../lib/icons.js";
4
- import { cn as f } from "../../lib/utils.js";
2
+ import { jsxs as m, jsx as n } from "react/jsx-runtime";
3
+ import { DropdownArrowIcon as V, SelectCheckIcon as ee, CheckboxCheckedIcon as te, CheckboxUncheckedIcon as re, RadioCheckedIcon as ne, RadioUncheckedIcon as se } from "../../lib/icons.js";
4
+ import { cn as x } from "../../lib/utils.js";
5
5
  import * as s from "react";
6
6
  import { Button as U } from "./button.js";
7
7
  const k = {
@@ -22,20 +22,20 @@ const k = {
22
22
  }
23
23
  }, ae = s.forwardRef(
24
24
  ({
25
- type: c = "default",
25
+ type: o = "default",
26
26
  options: i,
27
27
  value: R,
28
28
  onValueChange: K,
29
- values: m,
29
+ values: b,
30
30
  onValuesChange: T,
31
31
  placeholder: D = "선택해주세요",
32
- label: h,
32
+ label: p,
33
33
  showLabel: v = !0,
34
34
  require: E,
35
- size: g = "lg",
36
- disabled: x,
35
+ size: h = "lg",
36
+ disabled: d,
37
37
  readOnly: w,
38
- error: b,
38
+ error: u,
39
39
  hint: N,
40
40
  leftIcon: j,
41
41
  showButtons: q,
@@ -47,181 +47,184 @@ const k = {
47
47
  className: J,
48
48
  "aria-label": P
49
49
  }, Q) => {
50
- const [n, l] = s.useState(!1), [a, p] = s.useState(-1), [$, A] = s.useState("keyboard"), y = s.useRef(null), L = s.useRef(null), M = s.useRef(null), d = s.useId(), I = (e) => `${d}-option-${e}`;
50
+ const [t, c] = s.useState(!1), [a, g] = s.useState(-1), [$, A] = s.useState("keyboard"), y = s.useRef(null), L = s.useRef(null), M = s.useRef(null), f = s.useId(), I = (e) => `${f}-option-${e}`;
51
51
  s.useEffect(() => {
52
52
  const e = (r) => {
53
- y.current && !y.current.contains(r.target) && l(!1);
53
+ y.current && !y.current.contains(r.target) && c(!1);
54
54
  };
55
55
  return document.addEventListener("mousedown", e), () => document.removeEventListener("mousedown", e);
56
56
  }, []), s.useEffect(() => {
57
- n || p(-1);
58
- }, [n]), s.useEffect(() => {
59
- !n || a < 0 || L.current?.querySelector(`#${CSS.escape(I(a))}`)?.scrollIntoView({ block: "nearest" });
60
- }, [n, a]);
61
- const W = (e) => c === "checkbox" ? m?.includes(e) ?? !1 : e === R, C = i.find((e) => e.value === R), X = () => c === "checkbox" ? !m || m.length === 0 ? D : i.filter((r) => m.includes(r.value)).map((r) => r.label).join(", ") : C ? C.label : D, Y = () => {
62
- x || w || l((e) => !e);
57
+ t || g(-1);
58
+ }, [t]), s.useEffect(() => {
59
+ !t || a < 0 || L.current?.querySelector(`#${CSS.escape(I(a))}`)?.scrollIntoView({ block: "nearest" });
60
+ }, [t, a]);
61
+ const W = (e) => o === "checkbox" ? b?.includes(e) ?? !1 : e === R, C = i.find((e) => e.value === R), X = () => o === "checkbox" ? !b || b.length === 0 ? D : i.filter((r) => b.includes(r.value)).map((r) => r.label).join(", ") : C ? C.label : D, Y = () => {
62
+ d || w || c((e) => !e);
63
63
  }, O = (e) => {
64
- if (c === "checkbox") {
65
- const r = m ?? [], o = r.includes(e) ? r.filter((S) => S !== e) : [...r, e];
66
- T?.(o);
64
+ if (o === "checkbox") {
65
+ const r = b ?? [], l = r.includes(e) ? r.filter((S) => S !== e) : [...r, e];
66
+ T?.(l);
67
67
  } else
68
- K?.(e), l(!1);
69
- }, Z = c === "checkbox" ? (m?.length ?? 0) > 0 : !!C, _ = (e) => {
70
- if (x || w) return;
71
- const r = (o) => {
72
- e.preventDefault(), n || l(!0), A("keyboard"), p(o);
68
+ K?.(e), c(!1);
69
+ }, Z = o === "checkbox" ? (b?.length ?? 0) > 0 : !!C, _ = (e) => {
70
+ if (d || w) return;
71
+ const r = (l) => {
72
+ e.preventDefault(), t || c(!0), A("keyboard"), g(l);
73
73
  };
74
74
  switch (e.key) {
75
75
  case "ArrowDown":
76
- return r(n ? Math.min(a + 1, i.length - 1) : 0);
76
+ return r(t ? Math.min(a + 1, i.length - 1) : 0);
77
77
  case "ArrowUp":
78
- return r(n ? Math.max(a - 1, 0) : i.length - 1);
78
+ return r(t ? Math.max(a - 1, 0) : i.length - 1);
79
79
  case "Home":
80
- return n ? r(0) : void 0;
80
+ return t ? r(0) : void 0;
81
81
  case "End":
82
- return n ? r(i.length - 1) : void 0;
82
+ return t ? r(i.length - 1) : void 0;
83
83
  case "Enter":
84
84
  case " ":
85
- e.preventDefault(), n ? a >= 0 && O(i[a].value) : l(!0);
85
+ e.preventDefault(), t ? a >= 0 && O(i[a].value) : c(!0);
86
86
  return;
87
87
  case "Escape":
88
- n && (e.preventDefault(), l(!1), M.current?.focus());
88
+ t && (e.preventDefault(), c(!1), M.current?.focus());
89
89
  return;
90
90
  case "Tab":
91
- l(!1);
91
+ c(!1);
92
92
  return;
93
93
  default:
94
94
  return;
95
95
  }
96
96
  };
97
- return /* @__PURE__ */ u("div", { ref: Q, className: f("flex flex-col gap-2", G), children: [
98
- v && h && /* @__PURE__ */ u("div", { className: "flex items-start gap-0.5 whitespace-nowrap", children: [
99
- /* @__PURE__ */ t("span", { id: `${d}-label`, className: "text-body-2-r text-ds-text-strong", children: h }),
100
- E && /* @__PURE__ */ t("span", { "aria-hidden": "true", className: "text-body-2-r text-ds-primary-red-normal", children: "*" })
97
+ return /* @__PURE__ */ m("div", { ref: Q, className: x("flex flex-col gap-2", G), children: [
98
+ v && p && /* @__PURE__ */ m("div", { className: "flex items-start gap-0.5 whitespace-nowrap", children: [
99
+ /* @__PURE__ */ n("span", { id: `${f}-label`, className: "text-body-2-r text-ds-text-strong", children: p }),
100
+ E && /* @__PURE__ */ n("span", { "aria-hidden": "true", className: "text-body-2-r text-ds-primary-red-normal", children: "*" })
101
101
  ] }),
102
- /* @__PURE__ */ u("div", { ref: y, className: "relative", children: [
103
- /* @__PURE__ */ u(
102
+ /* @__PURE__ */ m("div", { ref: y, className: "relative", children: [
103
+ /* @__PURE__ */ m(
104
104
  "button",
105
105
  {
106
106
  ref: M,
107
107
  type: "button",
108
108
  role: "combobox",
109
- "aria-expanded": n,
109
+ "aria-expanded": t,
110
110
  "aria-haspopup": "listbox",
111
- "aria-controls": n ? d : void 0,
112
- "aria-activedescendant": n && a >= 0 ? I(a) : void 0,
113
- "aria-labelledby": v && h ? `${d}-label` : void 0,
114
- "aria-label": v && h ? void 0 : P,
115
- "aria-invalid": b ? !0 : void 0,
116
- "aria-describedby": b || N ? `${d}-description` : void 0,
111
+ "aria-controls": t ? f : void 0,
112
+ "aria-activedescendant": t && a >= 0 ? I(a) : void 0,
113
+ "aria-labelledby": v && p ? `${f}-label` : void 0,
114
+ "aria-label": v && p ? void 0 : P,
115
+ "aria-invalid": u ? !0 : void 0,
116
+ "aria-describedby": u || N ? `${f}-description` : void 0,
117
117
  "aria-required": E || void 0,
118
- disabled: x,
118
+ disabled: d,
119
119
  onClick: Y,
120
120
  onKeyDown: _,
121
- className: f(
121
+ className: x(
122
122
  "flex items-center w-full rounded-ds-md border transition-colors",
123
- x ? "bg-ds-surface-strong border-ds-line-normal cursor-not-allowed" : w ? "bg-ds-surface-normal border-ds-line-normal cursor-default" : b ? "bg-ds-background-default border-ds-primary-red-normal hover:bg-ds-surface-normal" : n ? "bg-ds-background-default border-ds-primary-blue-normal" : "bg-ds-background-default border-ds-line-normal hover:bg-ds-surface-normal hover:border-ds-primary-blue-normal",
124
- k[g].trigger,
123
+ // SearchField·InputField(src/lib/field-box.ts)와 같은 규칙. 트리거는 버튼이라 focus-within 대신
124
+ // 열림 상태와 focus-visible 로 표현한다. 배경은 disabled·readOnly 우선, 테두리는 error 우선
125
+ d ? "bg-ds-surface-strong cursor-not-allowed" : w ? "bg-ds-surface-strong cursor-default" : u || t ? "bg-ds-background-default" : "bg-ds-surface-normal",
126
+ u ? "border-ds-primary-red-normal" : d ? "border-transparent" : t ? "border-ds-primary-blue-normal" : "border-transparent focus-visible:outline-none focus-visible:border-ds-primary-blue-normal",
127
+ k[h].trigger,
125
128
  J
126
129
  ),
127
130
  children: [
128
- j && /* @__PURE__ */ t("span", { className: "shrink-0 flex items-center justify-center text-ds-icon-normal", children: j }),
129
- /* @__PURE__ */ t(
131
+ j && /* @__PURE__ */ n("span", { className: "shrink-0 flex items-center justify-center text-ds-icon-normal", children: j }),
132
+ /* @__PURE__ */ n(
130
133
  "span",
131
134
  {
132
- className: f(
135
+ className: x(
133
136
  "flex-1 min-w-0 text-left truncate font-sans",
134
- k[g].text,
135
- Z ? "text-ds-text-strong" : x ? "text-ds-text-disabled" : "text-ds-text-normal"
137
+ k[h].text,
138
+ Z ? "text-ds-text-strong" : d ? "text-ds-text-disabled" : "text-ds-text-normal"
136
139
  ),
137
140
  children: X()
138
141
  }
139
142
  ),
140
- /* @__PURE__ */ t(
143
+ /* @__PURE__ */ n(
141
144
  "span",
142
145
  {
143
- className: f(
146
+ className: x(
144
147
  "shrink-0 flex items-center justify-center transition-transform",
145
- n && "rotate-180",
146
- x ? "text-ds-text-disabled" : "text-ds-icon-strong"
148
+ t && "rotate-180",
149
+ d ? "text-ds-text-disabled" : "text-ds-icon-strong"
147
150
  ),
148
- children: /* @__PURE__ */ t(V, {})
151
+ children: /* @__PURE__ */ n(V, {})
149
152
  }
150
153
  )
151
154
  ]
152
155
  }
153
156
  ),
154
- n && /* @__PURE__ */ u(
157
+ t && /* @__PURE__ */ m(
155
158
  "ul",
156
159
  {
157
160
  ref: L,
158
- id: d,
161
+ id: f,
159
162
  role: "listbox",
160
- "aria-multiselectable": c === "checkbox" ? !0 : void 0,
163
+ "aria-multiselectable": o === "checkbox" ? !0 : void 0,
161
164
  className: "absolute top-full left-0 z-50 mt-1 min-w-full w-max max-w-sm bg-ds-background-default border border-ds-line-normal rounded-ds-md p-1 shadow-ds-default",
162
165
  onMouseLeave: () => {
163
- $ === "mouse" && p(-1);
166
+ $ === "mouse" && g(-1);
164
167
  },
165
168
  children: [
166
169
  i.map((e, r) => {
167
- const o = W(e.value), S = r === a;
170
+ const l = W(e.value), S = r === a;
168
171
  return (
169
172
  // 키보드 조작은 aria-activedescendant 패턴에 따라 combobox 트리거의
170
173
  // onKeyDown이 전담한다(옵션은 포커스를 받지 않는 것이 WAI-ARIA 권고).
171
174
  // 이 규칙은 그 패턴을 인식하지 못하므로 해당 줄만 해제한다.
172
175
  // eslint-disable-next-line jsx-a11y/click-events-have-key-events
173
- /* @__PURE__ */ u(
176
+ /* @__PURE__ */ m(
174
177
  "li",
175
178
  {
176
179
  id: I(r),
177
180
  role: "option",
178
- "aria-selected": o,
181
+ "aria-selected": l,
179
182
  onClick: () => O(e.value),
180
183
  onMouseEnter: () => {
181
- A("mouse"), p(r);
184
+ A("mouse"), g(r);
182
185
  },
183
- className: f(
186
+ className: x(
184
187
  "flex items-center rounded-ds-md cursor-pointer gap-1.5 transition-colors",
185
- k[g].option,
186
- o ? "bg-ds-surface-normal" : "bg-ds-background-default hover:bg-ds-surface-normal",
188
+ k[h].option,
189
+ l ? "bg-ds-surface-normal" : "bg-ds-background-default hover:bg-ds-surface-normal",
187
190
  // 키보드 이동 위치 표시 (마우스 hover와 구분되도록 링으로, 키보드 이동에만)
188
191
  S && $ === "keyboard" && "ring-1 ring-inset ring-ds-primary-blue-normal"
189
192
  ),
190
193
  children: [
191
- /* @__PURE__ */ t(
194
+ /* @__PURE__ */ n(
192
195
  "span",
193
196
  {
194
- className: f(
197
+ className: x(
195
198
  "flex-1 min-w-0 truncate font-sans",
196
- k[g].text,
199
+ k[h].text,
197
200
  "text-ds-text-strong"
198
201
  ),
199
202
  children: e.label
200
203
  }
201
204
  ),
202
- c === "default" && o && /* @__PURE__ */ t("span", { className: "shrink-0 flex items-center justify-center text-ds-text-strong", children: /* @__PURE__ */ t(ee, {}) }),
203
- c === "checkbox" && /* @__PURE__ */ t("span", { className: "shrink-0 flex items-center justify-center", children: o ? /* @__PURE__ */ t(re, {}) : /* @__PURE__ */ t(te, {}) }),
204
- c === "radio" && /* @__PURE__ */ t("span", { className: "shrink-0 flex items-center justify-center", children: o ? /* @__PURE__ */ t(ne, {}) : /* @__PURE__ */ t(se, {}) })
205
+ o === "default" && l && /* @__PURE__ */ n("span", { className: "shrink-0 flex items-center justify-center text-ds-text-strong", children: /* @__PURE__ */ n(ee, {}) }),
206
+ o === "checkbox" && /* @__PURE__ */ n("span", { className: "shrink-0 flex items-center justify-center", children: l ? /* @__PURE__ */ n(te, {}) : /* @__PURE__ */ n(re, {}) }),
207
+ o === "radio" && /* @__PURE__ */ n("span", { className: "shrink-0 flex items-center justify-center", children: l ? /* @__PURE__ */ n(ne, {}) : /* @__PURE__ */ n(se, {}) })
205
208
  ]
206
209
  },
207
210
  e.value
208
211
  )
209
212
  );
210
213
  }),
211
- q && /* @__PURE__ */ u("li", { className: "flex gap-2 pt-1", children: [
212
- /* @__PURE__ */ t(
214
+ q && /* @__PURE__ */ m("li", { className: "flex gap-2 pt-1", children: [
215
+ /* @__PURE__ */ n(
213
216
  U,
214
217
  {
215
218
  variant: "outlined",
216
219
  size: "lg",
217
220
  className: "flex-1",
218
221
  onClick: () => {
219
- H?.(), l(!1);
222
+ H?.(), c(!1);
220
223
  },
221
224
  children: z
222
225
  }
223
226
  ),
224
- /* @__PURE__ */ t(
227
+ /* @__PURE__ */ n(
225
228
  U,
226
229
  {
227
230
  variant: "filled",
@@ -229,7 +232,7 @@ const k = {
229
232
  size: "lg",
230
233
  className: "flex-1",
231
234
  onClick: () => {
232
- F?.(), l(!1);
235
+ F?.(), c(!1);
233
236
  },
234
237
  children: B
235
238
  }
@@ -239,15 +242,15 @@ const k = {
239
242
  }
240
243
  )
241
244
  ] }),
242
- (b || N) && /* @__PURE__ */ t(
245
+ (u || N) && /* @__PURE__ */ n(
243
246
  "p",
244
247
  {
245
- id: `${d}-description`,
246
- className: f(
248
+ id: `${f}-description`,
249
+ className: x(
247
250
  "text-body-4-r",
248
- b ? "text-ds-primary-red-normal" : "text-ds-text-normal"
251
+ u ? "text-ds-primary-red-normal" : "text-ds-text-normal"
249
252
  ),
250
- children: b ?? N
253
+ children: u ?? N
251
254
  }
252
255
  )
253
256
  ] });