@nurimedia/react-ui 0.1.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 (90) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +274 -0
  3. package/dist/AlertDialog/index.cjs +185 -0
  4. package/dist/AlertDialog/index.cjs.map +1 -0
  5. package/dist/AlertDialog/index.d.cts +49 -0
  6. package/dist/AlertDialog/index.d.ts +49 -0
  7. package/dist/AlertDialog/index.js +148 -0
  8. package/dist/AlertDialog/index.js.map +1 -0
  9. package/dist/AlertDialog/styles.css +187 -0
  10. package/dist/Button/index.cjs +136 -0
  11. package/dist/Button/index.cjs.map +1 -0
  12. package/dist/Button/index.d.cts +65 -0
  13. package/dist/Button/index.d.ts +65 -0
  14. package/dist/Button/index.js +109 -0
  15. package/dist/Button/index.js.map +1 -0
  16. package/dist/Button/styles.css +181 -0
  17. package/dist/Checkbox/index.cjs +83 -0
  18. package/dist/Checkbox/index.cjs.map +1 -0
  19. package/dist/Checkbox/index.d.cts +40 -0
  20. package/dist/Checkbox/index.d.ts +40 -0
  21. package/dist/Checkbox/index.js +46 -0
  22. package/dist/Checkbox/index.js.map +1 -0
  23. package/dist/Checkbox/styles.css +78 -0
  24. package/dist/Input/index.cjs +103 -0
  25. package/dist/Input/index.cjs.map +1 -0
  26. package/dist/Input/index.d.cts +53 -0
  27. package/dist/Input/index.d.ts +53 -0
  28. package/dist/Input/index.js +76 -0
  29. package/dist/Input/index.js.map +1 -0
  30. package/dist/Input/styles.css +135 -0
  31. package/dist/Modal/index.cjs +120 -0
  32. package/dist/Modal/index.cjs.map +1 -0
  33. package/dist/Modal/index.d.cts +62 -0
  34. package/dist/Modal/index.d.ts +62 -0
  35. package/dist/Modal/index.js +83 -0
  36. package/dist/Modal/index.js.map +1 -0
  37. package/dist/Modal/styles.css +229 -0
  38. package/dist/Radio/index.cjs +86 -0
  39. package/dist/Radio/index.cjs.map +1 -0
  40. package/dist/Radio/index.d.cts +46 -0
  41. package/dist/Radio/index.d.ts +46 -0
  42. package/dist/Radio/index.js +48 -0
  43. package/dist/Radio/index.js.map +1 -0
  44. package/dist/Radio/styles.css +76 -0
  45. package/dist/Select/index.cjs +116 -0
  46. package/dist/Select/index.cjs.map +1 -0
  47. package/dist/Select/index.d.cts +61 -0
  48. package/dist/Select/index.d.ts +61 -0
  49. package/dist/Select/index.js +79 -0
  50. package/dist/Select/index.js.map +1 -0
  51. package/dist/Select/styles.css +157 -0
  52. package/dist/Switch/index.cjs +71 -0
  53. package/dist/Switch/index.cjs.map +1 -0
  54. package/dist/Switch/index.d.cts +34 -0
  55. package/dist/Switch/index.d.ts +34 -0
  56. package/dist/Switch/index.js +34 -0
  57. package/dist/Switch/index.js.map +1 -0
  58. package/dist/Switch/styles.css +71 -0
  59. package/dist/Textarea/index.cjs +127 -0
  60. package/dist/Textarea/index.cjs.map +1 -0
  61. package/dist/Textarea/index.d.cts +48 -0
  62. package/dist/Textarea/index.d.ts +48 -0
  63. package/dist/Textarea/index.js +100 -0
  64. package/dist/Textarea/index.js.map +1 -0
  65. package/dist/Textarea/styles.css +95 -0
  66. package/dist/Toast/index.cjs +184 -0
  67. package/dist/Toast/index.cjs.map +1 -0
  68. package/dist/Toast/index.d.cts +67 -0
  69. package/dist/Toast/index.d.ts +67 -0
  70. package/dist/Toast/index.js +146 -0
  71. package/dist/Toast/index.js.map +1 -0
  72. package/dist/Toast/styles.css +122 -0
  73. package/dist/Tooltip/index.cjs +105 -0
  74. package/dist/Tooltip/index.cjs.map +1 -0
  75. package/dist/Tooltip/index.d.cts +65 -0
  76. package/dist/Tooltip/index.d.ts +65 -0
  77. package/dist/Tooltip/index.js +67 -0
  78. package/dist/Tooltip/index.js.map +1 -0
  79. package/dist/Tooltip/styles.css +42 -0
  80. package/dist/index.cjs +900 -0
  81. package/dist/index.cjs.map +1 -0
  82. package/dist/index.d.cts +16 -0
  83. package/dist/index.d.ts +16 -0
  84. package/dist/index.js +850 -0
  85. package/dist/index.js.map +1 -0
  86. package/dist/styles.css +11 -0
  87. package/dist/tokens.css +232 -0
  88. package/dist/typography.css +104 -0
  89. package/llms.txt +369 -0
  90. package/package.json +183 -0
@@ -0,0 +1,62 @@
1
+ import * as react from 'react';
2
+ import { ComponentPropsWithoutRef, ReactNode } from 'react';
3
+ import * as Dialog from '@radix-ui/react-dialog';
4
+
5
+ type Placement = 'center' | 'bottom';
6
+ type Size = 'sm' | 'md' | 'lg';
7
+ interface ModalContentProps extends ComponentPropsWithoutRef<typeof Dialog.Content> {
8
+ /**
9
+ * 표시 위치.
10
+ * - `center`: 화면 중앙 다이얼로그 (기본)
11
+ * - `bottom`: 하단 바텀시트 (모바일에서 아래에서 올라옴)
12
+ * @default 'center'
13
+ */
14
+ placement?: Placement;
15
+ /**
16
+ * 최대 너비. **`placement="center"`에서만 적용** (bottom 바텀시트는 항상 풀 width).
17
+ * @default 'md'
18
+ */
19
+ size?: Size;
20
+ /** 백드롭(오버레이) 표시 여부. @default true */
21
+ overlay?: boolean;
22
+ /** 포털 렌더 대상. 기본은 document.body. 다른 요소에 렌더하려면 지정. */
23
+ container?: HTMLElement | null;
24
+ }
25
+ interface ModalTitleProps extends ComponentPropsWithoutRef<typeof Dialog.Title> {
26
+ /**
27
+ * 시각적으로는 숨기되 스크린리더에는 노출. 시각적 제목이 없는 모달도
28
+ * 접근 가능한 이름을 가져야 하므로(Radix Dialog는 Title 필수), 이 옵션으로 처리.
29
+ * @example <Modal.Title visuallyHidden>이미지 미리보기</Modal.Title>
30
+ */
31
+ visuallyHidden?: boolean;
32
+ }
33
+ interface ModalFooterProps extends ComponentPropsWithoutRef<'div'> {
34
+ /**
35
+ * 액션 버튼 정렬.
36
+ * - `end`: 우측 (기본, 데스크탑 관례)
37
+ * - `start`: 좌측
38
+ * - `center`: 가운데
39
+ * - `between`: 양끝 분산 (예: 좌측 위험 액션 + 우측 확인)
40
+ * - `full`: 버튼들이 가로를 균등하게 채움 (모바일 전체너비)
41
+ * @default 'end'
42
+ */
43
+ align?: 'start' | 'center' | 'end' | 'between' | 'full';
44
+ }
45
+ declare const Modal: {
46
+ (props: ComponentPropsWithoutRef<typeof Dialog.Root>): react.JSX.Element;
47
+ displayName: string;
48
+ } & {
49
+ Trigger: react.ForwardRefExoticComponent<Dialog.DialogTriggerProps & react.RefAttributes<HTMLButtonElement>>;
50
+ Content: react.ForwardRefExoticComponent<ModalContentProps & react.RefAttributes<HTMLDivElement>>;
51
+ Header: react.ForwardRefExoticComponent<Omit<react.DetailedHTMLProps<react.HTMLAttributes<HTMLDivElement>, HTMLDivElement>, "ref"> & react.RefAttributes<HTMLDivElement>>;
52
+ Title: react.ForwardRefExoticComponent<ModalTitleProps & react.RefAttributes<HTMLHeadingElement>>;
53
+ Description: react.ForwardRefExoticComponent<Omit<Dialog.DialogDescriptionProps & react.RefAttributes<HTMLParagraphElement>, "ref"> & react.RefAttributes<HTMLParagraphElement>>;
54
+ Body: react.ForwardRefExoticComponent<Omit<react.DetailedHTMLProps<react.HTMLAttributes<HTMLDivElement>, HTMLDivElement>, "ref"> & react.RefAttributes<HTMLDivElement>>;
55
+ Footer: react.ForwardRefExoticComponent<ModalFooterProps & react.RefAttributes<HTMLDivElement>>;
56
+ Close: react.ForwardRefExoticComponent<Dialog.DialogCloseProps & react.RefAttributes<HTMLButtonElement>>;
57
+ CloseButton: react.ForwardRefExoticComponent<Omit<Omit<react.DetailedHTMLProps<react.ButtonHTMLAttributes<HTMLButtonElement>, HTMLButtonElement>, "ref">, "children"> & {
58
+ children?: ReactNode;
59
+ } & react.RefAttributes<HTMLButtonElement>>;
60
+ };
61
+
62
+ export { Modal, type ModalContentProps, type ModalFooterProps, type ModalTitleProps };
@@ -0,0 +1,83 @@
1
+ // src/Modal/Modal.tsx
2
+ import "./styles.css";
3
+ import * as Dialog from "@radix-ui/react-dialog";
4
+ import { forwardRef } from "react";
5
+
6
+ // src/utils/cx.ts
7
+ function cx(...classes) {
8
+ return classes.filter(Boolean).join(" ");
9
+ }
10
+
11
+ // src/Modal/Modal.tsx
12
+ import { jsx, jsxs } from "react/jsx-runtime";
13
+ var Root2 = (props) => /* @__PURE__ */ jsx(Dialog.Root, { ...props });
14
+ Root2.displayName = "Modal";
15
+ var Trigger2 = Dialog.Trigger;
16
+ var Content2 = forwardRef(
17
+ function ModalContent({ placement = "center", size = "md", overlay = true, container, className, children, ...rest }, ref) {
18
+ const classes = cx("nuri-modal", `nuri-modal--${placement}`, `nuri-modal--${size}`, className);
19
+ return /* @__PURE__ */ jsxs(Dialog.Portal, { container, children: [
20
+ overlay && /* @__PURE__ */ jsx(Dialog.Overlay, { className: "nuri-modal__overlay" }),
21
+ /* @__PURE__ */ jsx(Dialog.Content, { ref, className: classes, ...rest, children })
22
+ ] });
23
+ }
24
+ );
25
+ var Header = forwardRef(function ModalHeader({ className, ...rest }, ref) {
26
+ return /* @__PURE__ */ jsx("div", { ref, className: cx("nuri-modal__header", className), ...rest });
27
+ });
28
+ var Title2 = forwardRef(function ModalTitle({ visuallyHidden = false, className, ...rest }, ref) {
29
+ return /* @__PURE__ */ jsx(
30
+ Dialog.Title,
31
+ {
32
+ ref,
33
+ className: cx("nuri-modal__title", visuallyHidden && "nuri-sr-only", className),
34
+ ...rest
35
+ }
36
+ );
37
+ });
38
+ var Description2 = forwardRef(function ModalDescription({ className, ...rest }, ref) {
39
+ return /* @__PURE__ */ jsx(Dialog.Description, { ref, className: cx("nuri-modal__description", className), ...rest });
40
+ });
41
+ var Body = forwardRef(function ModalBody({ className, ...rest }, ref) {
42
+ return /* @__PURE__ */ jsx("div", { ref, className: cx("nuri-modal__body", className), ...rest });
43
+ });
44
+ var Footer = forwardRef(function ModalFooter({ align = "end", className, ...rest }, ref) {
45
+ return /* @__PURE__ */ jsx(
46
+ "div",
47
+ {
48
+ ref,
49
+ className: cx("nuri-modal__footer", `nuri-modal__footer--${align}`, className),
50
+ ...rest
51
+ }
52
+ );
53
+ });
54
+ var Close2 = Dialog.Close;
55
+ var CloseIcon = () => /* @__PURE__ */ jsx("svg", { width: "20", height: "20", viewBox: "0 0 20 20", fill: "none", "aria-hidden": "true", children: /* @__PURE__ */ jsx("path", { d: "M5 5l10 10M15 5L5 15", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round" }) });
56
+ var CloseButton = forwardRef(function ModalCloseButton({ className, "aria-label": ariaLabel = "\uB2EB\uAE30", children, ...rest }, ref) {
57
+ return /* @__PURE__ */ jsx(Dialog.Close, { asChild: true, children: /* @__PURE__ */ jsx(
58
+ "button",
59
+ {
60
+ ref,
61
+ type: "button",
62
+ "aria-label": ariaLabel,
63
+ className: cx("nuri-modal__close", className),
64
+ ...rest,
65
+ children: children ?? /* @__PURE__ */ jsx(CloseIcon, {})
66
+ }
67
+ ) });
68
+ });
69
+ var Modal = Object.assign(Root2, {
70
+ Trigger: Trigger2,
71
+ Content: Content2,
72
+ Header,
73
+ Title: Title2,
74
+ Description: Description2,
75
+ Body,
76
+ Footer,
77
+ Close: Close2,
78
+ CloseButton
79
+ });
80
+ export {
81
+ Modal
82
+ };
83
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/Modal/Modal.tsx","../../src/utils/cx.ts"],"sourcesContent":["'use client';\n\nimport './styles.css';\nimport * as Dialog from '@radix-ui/react-dialog';\nimport { forwardRef } from 'react';\nimport type { ComponentPropsWithoutRef, ElementRef, ReactNode } from 'react';\nimport { cx } from '../utils/cx.js';\n\ntype Placement = 'center' | 'bottom';\ntype Size = 'sm' | 'md' | 'lg';\n\n/**\n * 모달 루트. open/onOpenChange로 제어하거나, Modal.Trigger와 함께 비제어로도 사용.\n * Radix Dialog 기반 — 포커스 트랩·Esc·스크롤 락·포커스 복귀·aria 연결을 자동 제공.\n *\n * @example 제어형\n * <Modal open={open} onOpenChange={setOpen}>\n * <Modal.Content>...</Modal.Content>\n * </Modal>\n *\n * @example 비제어 (트리거)\n * <Modal>\n * <Modal.Trigger asChild><Button>열기</Button></Modal.Trigger>\n * <Modal.Content>...</Modal.Content>\n * </Modal>\n */\nconst Root = (props: ComponentPropsWithoutRef<typeof Dialog.Root>) => <Dialog.Root {...props} />;\nRoot.displayName = 'Modal';\n\n/** 모달을 여는 트리거. `asChild`로 임의 엘리먼트(Button 등)에 위임. */\nconst Trigger = Dialog.Trigger;\n\nexport interface ModalContentProps extends ComponentPropsWithoutRef<typeof Dialog.Content> {\n /**\n * 표시 위치.\n * - `center`: 화면 중앙 다이얼로그 (기본)\n * - `bottom`: 하단 바텀시트 (모바일에서 아래에서 올라옴)\n * @default 'center'\n */\n placement?: Placement;\n /**\n * 최대 너비. **`placement=\"center\"`에서만 적용** (bottom 바텀시트는 항상 풀 width).\n * @default 'md'\n */\n size?: Size;\n /** 백드롭(오버레이) 표시 여부. @default true */\n overlay?: boolean;\n /** 포털 렌더 대상. 기본은 document.body. 다른 요소에 렌더하려면 지정. */\n container?: HTMLElement | null;\n}\n\n/**\n * 오버레이 + 포털 + 다이얼로그 패널을 한 번에 렌더.\n * 내부에 Modal.Header / Modal.Body / Modal.Footer를 자유롭게 합성.\n */\nconst Content = forwardRef<ElementRef<typeof Dialog.Content>, ModalContentProps>(\n function ModalContent(\n { placement = 'center', size = 'md', overlay = true, container, className, children, ...rest },\n ref,\n ) {\n const classes = cx('nuri-modal', `nuri-modal--${placement}`, `nuri-modal--${size}`, className);\n\n return (\n <Dialog.Portal container={container}>\n {overlay && <Dialog.Overlay className=\"nuri-modal__overlay\" />}\n <Dialog.Content ref={ref} className={classes} {...rest}>\n {children}\n </Dialog.Content>\n </Dialog.Portal>\n );\n },\n);\n\n/** 헤더 영역 (Title/Description/CloseButton을 담는 레이아웃 슬롯). */\nconst Header = forwardRef<HTMLDivElement, ComponentPropsWithoutRef<'div'>>(function ModalHeader(\n { className, ...rest },\n ref,\n) {\n return <div ref={ref} className={cx('nuri-modal__header', className)} {...rest} />;\n});\n\nexport interface ModalTitleProps extends ComponentPropsWithoutRef<typeof Dialog.Title> {\n /**\n * 시각적으로는 숨기되 스크린리더에는 노출. 시각적 제목이 없는 모달도\n * 접근 가능한 이름을 가져야 하므로(Radix Dialog는 Title 필수), 이 옵션으로 처리.\n * @example <Modal.Title visuallyHidden>이미지 미리보기</Modal.Title>\n */\n visuallyHidden?: boolean;\n}\n\n/** 모달 제목. 렌더 시 자동으로 `aria-labelledby`로 연결됨 (Radix Dialog.Title). */\nconst Title = forwardRef<ElementRef<typeof Dialog.Title>, ModalTitleProps>(function ModalTitle(\n { visuallyHidden = false, className, ...rest },\n ref,\n) {\n return (\n <Dialog.Title\n ref={ref}\n className={cx('nuri-modal__title', visuallyHidden && 'nuri-sr-only', className)}\n {...rest}\n />\n );\n});\n\n/** 모달 설명. 렌더 시 자동으로 `aria-describedby`로 연결됨 (Radix Dialog.Description). */\nconst Description = forwardRef<\n ElementRef<typeof Dialog.Description>,\n ComponentPropsWithoutRef<typeof Dialog.Description>\n>(function ModalDescription({ className, ...rest }, ref) {\n return (\n <Dialog.Description ref={ref} className={cx('nuri-modal__description', className)} {...rest} />\n );\n});\n\n/** 본문 영역 (스크롤 가능). */\nconst Body = forwardRef<HTMLDivElement, ComponentPropsWithoutRef<'div'>>(function ModalBody(\n { className, ...rest },\n ref,\n) {\n return <div ref={ref} className={cx('nuri-modal__body', className)} {...rest} />;\n});\n\nexport interface ModalFooterProps extends ComponentPropsWithoutRef<'div'> {\n /**\n * 액션 버튼 정렬.\n * - `end`: 우측 (기본, 데스크탑 관례)\n * - `start`: 좌측\n * - `center`: 가운데\n * - `between`: 양끝 분산 (예: 좌측 위험 액션 + 우측 확인)\n * - `full`: 버튼들이 가로를 균등하게 채움 (모바일 전체너비)\n * @default 'end'\n */\n align?: 'start' | 'center' | 'end' | 'between' | 'full';\n}\n\n/** 푸터 영역 (액션 버튼 배치). 정렬은 `align` prop으로 제어. */\nconst Footer = forwardRef<HTMLDivElement, ModalFooterProps>(function ModalFooter(\n { align = 'end', className, ...rest },\n ref,\n) {\n return (\n <div\n ref={ref}\n className={cx('nuri-modal__footer', `nuri-modal__footer--${align}`, className)}\n {...rest}\n />\n );\n});\n\n/**\n * 모달을 닫는 액션. `asChild`로 임의 엘리먼트에 위임.\n * @example <Modal.Close asChild><Button variant=\"outline\">취소</Button></Modal.Close>\n */\nconst Close = Dialog.Close;\n\nconst CloseIcon = () => (\n <svg width=\"20\" height=\"20\" viewBox=\"0 0 20 20\" fill=\"none\" aria-hidden=\"true\">\n <path d=\"M5 5l10 10M15 5L5 15\" stroke=\"currentColor\" strokeWidth=\"1.5\" strokeLinecap=\"round\" />\n </svg>\n);\n\n/** 우상단 X 닫기 버튼 (미리 스타일링됨). 기본 `aria-label=\"닫기\"`. */\nconst CloseButton = forwardRef<\n HTMLButtonElement,\n Omit<ComponentPropsWithoutRef<'button'>, 'children'> & { children?: ReactNode }\n>(function ModalCloseButton(\n { className, 'aria-label': ariaLabel = '닫기', children, ...rest },\n ref,\n) {\n return (\n <Dialog.Close asChild>\n <button\n ref={ref}\n type=\"button\"\n aria-label={ariaLabel}\n className={cx('nuri-modal__close', className)}\n {...rest}\n >\n {children ?? <CloseIcon />}\n </button>\n </Dialog.Close>\n );\n});\n\nexport const Modal = Object.assign(Root, {\n Trigger,\n Content,\n Header,\n Title,\n Description,\n Body,\n Footer,\n Close,\n CloseButton,\n});\n","/**\n * 조건부 className 합성 유틸. falsy 값(undefined/false/null/'')은 제거.\n * @example cx('btn', isActive && 'btn--active', className)\n */\nexport function cx(...classes: Array<string | false | null | undefined>): string {\n return classes.filter(Boolean).join(' ');\n}\n"],"mappings":";AAEA,OAAO;AACP,YAAY,YAAY;AACxB,SAAS,kBAAkB;;;ACApB,SAAS,MAAM,SAA2D;AAC/E,SAAO,QAAQ,OAAO,OAAO,EAAE,KAAK,GAAG;AACzC;;;ADoBsE,cAqChE,YArCgE;AAAtE,IAAMA,QAAO,CAAC,UAAwD,oBAAQ,aAAP,EAAa,GAAG,OAAO;AAC9FA,MAAK,cAAc;AAGnB,IAAMC,WAAiB;AAyBvB,IAAMC,WAAU;AAAA,EACd,SAAS,aACP,EAAE,YAAY,UAAU,OAAO,MAAM,UAAU,MAAM,WAAW,WAAW,UAAU,GAAG,KAAK,GAC7F,KACA;AACA,UAAM,UAAU,GAAG,cAAc,eAAe,SAAS,IAAI,eAAe,IAAI,IAAI,SAAS;AAE7F,WACE,qBAAQ,eAAP,EAAc,WACZ;AAAA,iBAAW,oBAAQ,gBAAP,EAAe,WAAU,uBAAsB;AAAA,MAC5D,oBAAQ,gBAAP,EAAe,KAAU,WAAW,SAAU,GAAG,MAC/C,UACH;AAAA,OACF;AAAA,EAEJ;AACF;AAGA,IAAM,SAAS,WAA4D,SAAS,YAClF,EAAE,WAAW,GAAG,KAAK,GACrB,KACA;AACA,SAAO,oBAAC,SAAI,KAAU,WAAW,GAAG,sBAAsB,SAAS,GAAI,GAAG,MAAM;AAClF,CAAC;AAYD,IAAMC,SAAQ,WAA6D,SAAS,WAClF,EAAE,iBAAiB,OAAO,WAAW,GAAG,KAAK,GAC7C,KACA;AACA,SACE;AAAA,IAAQ;AAAA,IAAP;AAAA,MACC;AAAA,MACA,WAAW,GAAG,qBAAqB,kBAAkB,gBAAgB,SAAS;AAAA,MAC7E,GAAG;AAAA;AAAA,EACN;AAEJ,CAAC;AAGD,IAAMC,eAAc,WAGlB,SAAS,iBAAiB,EAAE,WAAW,GAAG,KAAK,GAAG,KAAK;AACvD,SACE,oBAAQ,oBAAP,EAAmB,KAAU,WAAW,GAAG,2BAA2B,SAAS,GAAI,GAAG,MAAM;AAEjG,CAAC;AAGD,IAAM,OAAO,WAA4D,SAAS,UAChF,EAAE,WAAW,GAAG,KAAK,GACrB,KACA;AACA,SAAO,oBAAC,SAAI,KAAU,WAAW,GAAG,oBAAoB,SAAS,GAAI,GAAG,MAAM;AAChF,CAAC;AAgBD,IAAM,SAAS,WAA6C,SAAS,YACnE,EAAE,QAAQ,OAAO,WAAW,GAAG,KAAK,GACpC,KACA;AACA,SACE;AAAA,IAAC;AAAA;AAAA,MACC;AAAA,MACA,WAAW,GAAG,sBAAsB,uBAAuB,KAAK,IAAI,SAAS;AAAA,MAC5E,GAAG;AAAA;AAAA,EACN;AAEJ,CAAC;AAMD,IAAMC,SAAe;AAErB,IAAM,YAAY,MAChB,oBAAC,SAAI,OAAM,MAAK,QAAO,MAAK,SAAQ,aAAY,MAAK,QAAO,eAAY,QACtE,8BAAC,UAAK,GAAE,wBAAuB,QAAO,gBAAe,aAAY,OAAM,eAAc,SAAQ,GAC/F;AAIF,IAAM,cAAc,WAGlB,SAAS,iBACT,EAAE,WAAW,cAAc,YAAY,gBAAM,UAAU,GAAG,KAAK,GAC/D,KACA;AACA,SACE,oBAAQ,cAAP,EAAa,SAAO,MACnB;AAAA,IAAC;AAAA;AAAA,MACC;AAAA,MACA,MAAK;AAAA,MACL,cAAY;AAAA,MACZ,WAAW,GAAG,qBAAqB,SAAS;AAAA,MAC3C,GAAG;AAAA,MAEH,sBAAY,oBAAC,aAAU;AAAA;AAAA,EAC1B,GACF;AAEJ,CAAC;AAEM,IAAM,QAAQ,OAAO,OAAOL,OAAM;AAAA,EACvC,SAAAC;AAAA,EACA,SAAAC;AAAA,EACA;AAAA,EACA,OAAAC;AAAA,EACA,aAAAC;AAAA,EACA;AAAA,EACA;AAAA,EACA,OAAAC;AAAA,EACA;AACF,CAAC;","names":["Root","Trigger","Content","Title","Description","Close"]}
@@ -0,0 +1,229 @@
1
+ .nuri-modal__overlay {
2
+ position: fixed;
3
+ inset: 0;
4
+ background-color: var(--nuri-modal-overlay);
5
+ z-index: var(--nuri-modal-z-index);
6
+ animation: nuri-modal-overlay-in 200ms ease;
7
+ }
8
+ .nuri-modal__overlay[data-state='closed'] {
9
+ animation: nuri-modal-overlay-out 150ms ease;
10
+ }
11
+
12
+ .nuri-modal,
13
+ .nuri-modal__header,
14
+ .nuri-modal__body,
15
+ .nuri-modal__footer {
16
+ box-sizing: border-box;
17
+ }
18
+
19
+ .nuri-modal {
20
+ position: fixed;
21
+ z-index: calc(var(--nuri-modal-z-index) + 1);
22
+ display: flex;
23
+ flex-direction: column;
24
+ gap: var(--nuri-modal-gap);
25
+ background-color: var(--nuri-modal-surface);
26
+ color: var(--nuri-modal-fg);
27
+ box-shadow: var(--nuri-modal-shadow);
28
+ font-family: inherit;
29
+ max-height: calc(100dvh - 48px);
30
+ outline: none;
31
+ }
32
+
33
+ /* ─── placement: center ─── */
34
+ .nuri-modal--center {
35
+ top: 50%;
36
+ left: 50%;
37
+ transform: translate(-50%, -50%);
38
+ width: calc(100vw - 32px);
39
+ border-radius: var(--nuri-modal-radius);
40
+ padding: var(--nuri-modal-padding);
41
+ animation: nuri-modal-center-in 200ms ease;
42
+ }
43
+ .nuri-modal--center[data-state='closed'] {
44
+ animation: nuri-modal-center-out 150ms ease;
45
+ }
46
+ .nuri-modal--center.nuri-modal--sm {
47
+ max-width: var(--nuri-modal-width-sm);
48
+ }
49
+ .nuri-modal--center.nuri-modal--md {
50
+ max-width: var(--nuri-modal-width-md);
51
+ }
52
+ .nuri-modal--center.nuri-modal--lg {
53
+ max-width: var(--nuri-modal-width-lg);
54
+ }
55
+
56
+ /* ─── placement: bottom (바텀시트) ─── */
57
+ /* 바텀시트는 모바일 패턴(언제 쓸지는 소비 앱이 useMediaQuery로 결정) → 항상 풀 width.
58
+ size prop은 바텀시트에선 width에 영향 없음 (center 전용) */
59
+ .nuri-modal--bottom {
60
+ left: 0;
61
+ right: 0;
62
+ bottom: 0;
63
+ width: 100%;
64
+ border-radius: var(--nuri-modal-radius-bottom) var(--nuri-modal-radius-bottom) 0 0;
65
+ padding: var(--nuri-modal-padding);
66
+ padding-bottom: calc(var(--nuri-modal-padding) + env(safe-area-inset-bottom, 0px));
67
+ animation: nuri-modal-bottom-in 240ms cubic-bezier(0.32, 0.72, 0, 1);
68
+ }
69
+ .nuri-modal--bottom[data-state='closed'] {
70
+ animation: nuri-modal-bottom-out 200ms ease;
71
+ }
72
+
73
+ /* ─── 내부 슬롯 ─── */
74
+ .nuri-modal__header {
75
+ display: flex;
76
+ flex-direction: column;
77
+ gap: var(--nuri-modal-header-gap);
78
+ position: relative;
79
+ }
80
+
81
+ .nuri-modal__title {
82
+ margin: 0;
83
+ font-size: var(--nuri-modal-title-size);
84
+ font-weight: var(--nuri-modal-title-weight);
85
+ color: var(--nuri-modal-fg);
86
+ line-height: 1.6;
87
+ }
88
+
89
+ /* 시각적으로만 숨김 (스크린리더에는 노출) — visuallyHidden Title 등에 사용 */
90
+ .nuri-sr-only {
91
+ position: absolute;
92
+ width: 1px;
93
+ height: 1px;
94
+ padding: 0;
95
+ margin: -1px;
96
+ overflow: hidden;
97
+ clip: rect(0, 0, 0, 0);
98
+ white-space: nowrap;
99
+ border: 0;
100
+ }
101
+
102
+ .nuri-modal__description {
103
+ margin: 0;
104
+ font-size: var(--nuri-modal-desc-size);
105
+ color: var(--nuri-modal-fg-muted);
106
+ line-height: 1.6;
107
+ }
108
+
109
+ .nuri-modal__body {
110
+ flex: 1 1 auto;
111
+ overflow-y: auto;
112
+ font-size: var(--nuri-modal-body-size);
113
+ line-height: 1.6;
114
+ }
115
+
116
+ /* ─── footer: 정렬은 align prop으로 (기본 end) ─── */
117
+ .nuri-modal__footer {
118
+ display: flex;
119
+ gap: var(--nuri-modal-footer-gap);
120
+ flex-shrink: 0;
121
+ }
122
+ .nuri-modal__footer--start {
123
+ justify-content: flex-start;
124
+ }
125
+ .nuri-modal__footer--center {
126
+ justify-content: center;
127
+ }
128
+ .nuri-modal__footer--end {
129
+ justify-content: flex-end;
130
+ }
131
+ .nuri-modal__footer--between {
132
+ justify-content: space-between;
133
+ }
134
+ /* full: 자식 버튼들이 가로를 균등하게 채움 (모바일 전체너비 액션) */
135
+ .nuri-modal__footer--full > * {
136
+ flex: 1 1 0;
137
+ }
138
+
139
+ /* ─── 우상단 닫기 버튼 ─── */
140
+ .nuri-modal__close {
141
+ position: absolute;
142
+ top: 0;
143
+ right: 0;
144
+ display: inline-flex;
145
+ align-items: center;
146
+ justify-content: center;
147
+ width: 28px;
148
+ height: 28px;
149
+ padding: 0;
150
+ border: none;
151
+ border-radius: 6px;
152
+ background: transparent;
153
+ color: var(--nuri-modal-close-color);
154
+ cursor: pointer;
155
+ transition:
156
+ background-color 120ms ease,
157
+ color 120ms ease;
158
+ }
159
+ .nuri-modal__close:hover {
160
+ background-color: var(--nuri-modal-close-bg-hover);
161
+ color: var(--nuri-modal-close-color-hover);
162
+ }
163
+ .nuri-modal__close:focus-visible {
164
+ outline: var(--nuri-button-focus-ring-width) solid
165
+ var(--nuri-button-focus-ring-color, var(--nuri-color-brand-default));
166
+ outline-offset: 2px;
167
+ }
168
+
169
+ /* ─── 애니메이션 ─── */
170
+ @keyframes nuri-modal-overlay-in {
171
+ from {
172
+ opacity: 0;
173
+ }
174
+ to {
175
+ opacity: 1;
176
+ }
177
+ }
178
+ @keyframes nuri-modal-overlay-out {
179
+ from {
180
+ opacity: 1;
181
+ }
182
+ to {
183
+ opacity: 0;
184
+ }
185
+ }
186
+ @keyframes nuri-modal-center-in {
187
+ from {
188
+ opacity: 0;
189
+ transform: translate(-50%, -50%) scale(0.96);
190
+ }
191
+ to {
192
+ opacity: 1;
193
+ transform: translate(-50%, -50%) scale(1);
194
+ }
195
+ }
196
+ @keyframes nuri-modal-center-out {
197
+ from {
198
+ opacity: 1;
199
+ transform: translate(-50%, -50%) scale(1);
200
+ }
201
+ to {
202
+ opacity: 0;
203
+ transform: translate(-50%, -50%) scale(0.96);
204
+ }
205
+ }
206
+ @keyframes nuri-modal-bottom-in {
207
+ from {
208
+ transform: translateY(100%);
209
+ }
210
+ to {
211
+ transform: translateY(0);
212
+ }
213
+ }
214
+ @keyframes nuri-modal-bottom-out {
215
+ from {
216
+ transform: translateY(0);
217
+ }
218
+ to {
219
+ transform: translateY(100%);
220
+ }
221
+ }
222
+
223
+ @media (prefers-reduced-motion: reduce) {
224
+ .nuri-modal__overlay,
225
+ .nuri-modal--center,
226
+ .nuri-modal--bottom {
227
+ animation-duration: 1ms;
228
+ }
229
+ }
@@ -0,0 +1,86 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/Radio/index.ts
31
+ var Radio_exports = {};
32
+ __export(Radio_exports, {
33
+ Radio: () => Radio,
34
+ RadioGroup: () => RadioGroup
35
+ });
36
+ module.exports = __toCommonJS(Radio_exports);
37
+
38
+ // src/Radio/Radio.tsx
39
+ var import_styles = require("./styles.css");
40
+ var RadixRadioGroup = __toESM(require("@radix-ui/react-radio-group"), 1);
41
+ var import_react = require("react");
42
+
43
+ // src/utils/cx.ts
44
+ function cx(...classes) {
45
+ return classes.filter(Boolean).join(" ");
46
+ }
47
+
48
+ // src/Radio/Radio.tsx
49
+ var import_jsx_runtime = require("react/jsx-runtime");
50
+ var RadioGroup = (0, import_react.forwardRef)(function RadioGroup2({ children, orientation = "vertical", className, ...rest }, ref) {
51
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
52
+ RadixRadioGroup.Root,
53
+ {
54
+ ref,
55
+ orientation,
56
+ className: cx("nuri-radio-group", `nuri-radio-group--${orientation}`, className),
57
+ ...rest,
58
+ children
59
+ }
60
+ );
61
+ });
62
+ var Radio = (0, import_react.forwardRef)(function Radio2({ value, label, disabled = false, id: idProp, className, ...rest }, ref) {
63
+ const reactId = (0, import_react.useId)();
64
+ const id = idProp ?? `nuri-radio-${reactId}`;
65
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: cx("nuri-radio", className), children: [
66
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
67
+ RadixRadioGroup.Item,
68
+ {
69
+ ref,
70
+ id,
71
+ value,
72
+ disabled,
73
+ className: "nuri-radio__control",
74
+ ...rest,
75
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(RadixRadioGroup.Indicator, { className: "nuri-radio__indicator" })
76
+ }
77
+ ),
78
+ label != null && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("label", { htmlFor: id, className: "nuri-radio__label", children: label })
79
+ ] });
80
+ });
81
+ // Annotate the CommonJS export names for ESM import in node:
82
+ 0 && (module.exports = {
83
+ Radio,
84
+ RadioGroup
85
+ });
86
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/Radio/index.ts","../../src/Radio/Radio.tsx","../../src/utils/cx.ts"],"sourcesContent":["export { RadioGroup, Radio } from './Radio.js';\nexport type { RadioGroupProps, RadioProps } from './Radio.js';\n","'use client';\n\nimport './styles.css';\nimport * as RadixRadioGroup from '@radix-ui/react-radio-group';\nimport { forwardRef, useId } from 'react';\nimport type { ReactNode } from 'react';\nimport { cx } from '../utils/cx.js';\n\nexport interface RadioGroupProps {\n children: ReactNode;\n /** 선택된 값 (제어형). */\n value?: string;\n /** 초기 값 (비제어형). */\n defaultValue?: string;\n onValueChange?: (value: string) => void;\n /** 배치 방향 — 키보드 화살표 탐색 방향에도 반영됨. @default 'vertical' */\n orientation?: 'vertical' | 'horizontal';\n disabled?: boolean;\n /** 폼 전송용 name. */\n name?: string;\n required?: boolean;\n className?: string;\n /** 그룹의 접근 가능한 이름 (시각 제목이 없을 때 필수). */\n 'aria-label'?: string;\n 'aria-labelledby'?: string;\n}\n\n/**\n * 라디오 그룹. Radix RadioGroup 기반 — 화살표 키 그룹 탐색·단일 선택·폼 참여 자동.\n * 그룹에 `aria-label`(또는 aria-labelledby)을 지정한다.\n *\n * @example\n * <RadioGroup aria-label=\"정렬\" value={sort} onValueChange={setSort}>\n * <Radio value=\"latest\" label=\"최신순\" />\n * <Radio value=\"edited\" label=\"수정순\" />\n * </RadioGroup>\n */\nexport const RadioGroup = forwardRef<HTMLDivElement, RadioGroupProps>(function RadioGroup(\n { children, orientation = 'vertical', className, ...rest },\n ref,\n) {\n return (\n <RadixRadioGroup.Root\n ref={ref}\n orientation={orientation}\n className={cx('nuri-radio-group', `nuri-radio-group--${orientation}`, className)}\n {...rest}\n >\n {children}\n </RadixRadioGroup.Root>\n );\n});\n\nexport interface RadioProps {\n /** 이 항목이 나타내는 값. */\n value: string;\n /** 라벨 (컨트롤 우측). 없으면 `aria-label` 필수. */\n label?: ReactNode;\n disabled?: boolean;\n id?: string;\n className?: string;\n 'aria-label'?: string;\n}\n\n/** 라디오 항목 — 반드시 RadioGroup 안에서 사용. */\nexport const Radio = forwardRef<HTMLButtonElement, RadioProps>(function Radio(\n { value, label, disabled = false, id: idProp, className, ...rest },\n ref,\n) {\n const reactId = useId();\n const id = idProp ?? `nuri-radio-${reactId}`;\n\n return (\n <div className={cx('nuri-radio', className)}>\n <RadixRadioGroup.Item\n ref={ref}\n id={id}\n value={value}\n disabled={disabled}\n className=\"nuri-radio__control\"\n {...rest}\n >\n <RadixRadioGroup.Indicator className=\"nuri-radio__indicator\" />\n </RadixRadioGroup.Item>\n {label != null && (\n <label htmlFor={id} className=\"nuri-radio__label\">\n {label}\n </label>\n )}\n </div>\n );\n});\n","/**\n * 조건부 className 합성 유틸. falsy 값(undefined/false/null/'')은 제거.\n * @example cx('btn', isActive && 'btn--active', className)\n */\nexport function cx(...classes: Array<string | false | null | undefined>): string {\n return classes.filter(Boolean).join(' ');\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACEA,oBAAO;AACP,sBAAiC;AACjC,mBAAkC;;;ACA3B,SAAS,MAAM,SAA2D;AAC/E,SAAO,QAAQ,OAAO,OAAO,EAAE,KAAK,GAAG;AACzC;;;ADoCI;AALG,IAAM,iBAAa,yBAA4C,SAASA,YAC7E,EAAE,UAAU,cAAc,YAAY,WAAW,GAAG,KAAK,GACzD,KACA;AACA,SACE;AAAA,IAAiB;AAAA,IAAhB;AAAA,MACC;AAAA,MACA;AAAA,MACA,WAAW,GAAG,oBAAoB,qBAAqB,WAAW,IAAI,SAAS;AAAA,MAC9E,GAAG;AAAA,MAEH;AAAA;AAAA,EACH;AAEJ,CAAC;AAcM,IAAM,YAAQ,yBAA0C,SAASC,OACtE,EAAE,OAAO,OAAO,WAAW,OAAO,IAAI,QAAQ,WAAW,GAAG,KAAK,GACjE,KACA;AACA,QAAM,cAAU,oBAAM;AACtB,QAAM,KAAK,UAAU,cAAc,OAAO;AAE1C,SACE,6CAAC,SAAI,WAAW,GAAG,cAAc,SAAS,GACxC;AAAA;AAAA,MAAiB;AAAA,MAAhB;AAAA,QACC;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA,WAAU;AAAA,QACT,GAAG;AAAA,QAEJ,sDAAiB,2BAAhB,EAA0B,WAAU,yBAAwB;AAAA;AAAA,IAC/D;AAAA,IACC,SAAS,QACR,4CAAC,WAAM,SAAS,IAAI,WAAU,qBAC3B,iBACH;AAAA,KAEJ;AAEJ,CAAC;","names":["RadioGroup","Radio"]}
@@ -0,0 +1,46 @@
1
+ import * as react from 'react';
2
+ import { ReactNode } from 'react';
3
+
4
+ interface RadioGroupProps {
5
+ children: ReactNode;
6
+ /** 선택된 값 (제어형). */
7
+ value?: string;
8
+ /** 초기 값 (비제어형). */
9
+ defaultValue?: string;
10
+ onValueChange?: (value: string) => void;
11
+ /** 배치 방향 — 키보드 화살표 탐색 방향에도 반영됨. @default 'vertical' */
12
+ orientation?: 'vertical' | 'horizontal';
13
+ disabled?: boolean;
14
+ /** 폼 전송용 name. */
15
+ name?: string;
16
+ required?: boolean;
17
+ className?: string;
18
+ /** 그룹의 접근 가능한 이름 (시각 제목이 없을 때 필수). */
19
+ 'aria-label'?: string;
20
+ 'aria-labelledby'?: string;
21
+ }
22
+ /**
23
+ * 라디오 그룹. Radix RadioGroup 기반 — 화살표 키 그룹 탐색·단일 선택·폼 참여 자동.
24
+ * 그룹에 `aria-label`(또는 aria-labelledby)을 지정한다.
25
+ *
26
+ * @example
27
+ * <RadioGroup aria-label="정렬" value={sort} onValueChange={setSort}>
28
+ * <Radio value="latest" label="최신순" />
29
+ * <Radio value="edited" label="수정순" />
30
+ * </RadioGroup>
31
+ */
32
+ declare const RadioGroup: react.ForwardRefExoticComponent<RadioGroupProps & react.RefAttributes<HTMLDivElement>>;
33
+ interface RadioProps {
34
+ /** 이 항목이 나타내는 값. */
35
+ value: string;
36
+ /** 라벨 (컨트롤 우측). 없으면 `aria-label` 필수. */
37
+ label?: ReactNode;
38
+ disabled?: boolean;
39
+ id?: string;
40
+ className?: string;
41
+ 'aria-label'?: string;
42
+ }
43
+ /** 라디오 항목 — 반드시 RadioGroup 안에서 사용. */
44
+ declare const Radio: react.ForwardRefExoticComponent<RadioProps & react.RefAttributes<HTMLButtonElement>>;
45
+
46
+ export { Radio, RadioGroup, type RadioGroupProps, type RadioProps };
@@ -0,0 +1,46 @@
1
+ import * as react from 'react';
2
+ import { ReactNode } from 'react';
3
+
4
+ interface RadioGroupProps {
5
+ children: ReactNode;
6
+ /** 선택된 값 (제어형). */
7
+ value?: string;
8
+ /** 초기 값 (비제어형). */
9
+ defaultValue?: string;
10
+ onValueChange?: (value: string) => void;
11
+ /** 배치 방향 — 키보드 화살표 탐색 방향에도 반영됨. @default 'vertical' */
12
+ orientation?: 'vertical' | 'horizontal';
13
+ disabled?: boolean;
14
+ /** 폼 전송용 name. */
15
+ name?: string;
16
+ required?: boolean;
17
+ className?: string;
18
+ /** 그룹의 접근 가능한 이름 (시각 제목이 없을 때 필수). */
19
+ 'aria-label'?: string;
20
+ 'aria-labelledby'?: string;
21
+ }
22
+ /**
23
+ * 라디오 그룹. Radix RadioGroup 기반 — 화살표 키 그룹 탐색·단일 선택·폼 참여 자동.
24
+ * 그룹에 `aria-label`(또는 aria-labelledby)을 지정한다.
25
+ *
26
+ * @example
27
+ * <RadioGroup aria-label="정렬" value={sort} onValueChange={setSort}>
28
+ * <Radio value="latest" label="최신순" />
29
+ * <Radio value="edited" label="수정순" />
30
+ * </RadioGroup>
31
+ */
32
+ declare const RadioGroup: react.ForwardRefExoticComponent<RadioGroupProps & react.RefAttributes<HTMLDivElement>>;
33
+ interface RadioProps {
34
+ /** 이 항목이 나타내는 값. */
35
+ value: string;
36
+ /** 라벨 (컨트롤 우측). 없으면 `aria-label` 필수. */
37
+ label?: ReactNode;
38
+ disabled?: boolean;
39
+ id?: string;
40
+ className?: string;
41
+ 'aria-label'?: string;
42
+ }
43
+ /** 라디오 항목 — 반드시 RadioGroup 안에서 사용. */
44
+ declare const Radio: react.ForwardRefExoticComponent<RadioProps & react.RefAttributes<HTMLButtonElement>>;
45
+
46
+ export { Radio, RadioGroup, type RadioGroupProps, type RadioProps };
@@ -0,0 +1,48 @@
1
+ // src/Radio/Radio.tsx
2
+ import "./styles.css";
3
+ import * as RadixRadioGroup from "@radix-ui/react-radio-group";
4
+ import { forwardRef, useId } from "react";
5
+
6
+ // src/utils/cx.ts
7
+ function cx(...classes) {
8
+ return classes.filter(Boolean).join(" ");
9
+ }
10
+
11
+ // src/Radio/Radio.tsx
12
+ import { jsx, jsxs } from "react/jsx-runtime";
13
+ var RadioGroup = forwardRef(function RadioGroup2({ children, orientation = "vertical", className, ...rest }, ref) {
14
+ return /* @__PURE__ */ jsx(
15
+ RadixRadioGroup.Root,
16
+ {
17
+ ref,
18
+ orientation,
19
+ className: cx("nuri-radio-group", `nuri-radio-group--${orientation}`, className),
20
+ ...rest,
21
+ children
22
+ }
23
+ );
24
+ });
25
+ var Radio = forwardRef(function Radio2({ value, label, disabled = false, id: idProp, className, ...rest }, ref) {
26
+ const reactId = useId();
27
+ const id = idProp ?? `nuri-radio-${reactId}`;
28
+ return /* @__PURE__ */ jsxs("div", { className: cx("nuri-radio", className), children: [
29
+ /* @__PURE__ */ jsx(
30
+ RadixRadioGroup.Item,
31
+ {
32
+ ref,
33
+ id,
34
+ value,
35
+ disabled,
36
+ className: "nuri-radio__control",
37
+ ...rest,
38
+ children: /* @__PURE__ */ jsx(RadixRadioGroup.Indicator, { className: "nuri-radio__indicator" })
39
+ }
40
+ ),
41
+ label != null && /* @__PURE__ */ jsx("label", { htmlFor: id, className: "nuri-radio__label", children: label })
42
+ ] });
43
+ });
44
+ export {
45
+ Radio,
46
+ RadioGroup
47
+ };
48
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/Radio/Radio.tsx","../../src/utils/cx.ts"],"sourcesContent":["'use client';\n\nimport './styles.css';\nimport * as RadixRadioGroup from '@radix-ui/react-radio-group';\nimport { forwardRef, useId } from 'react';\nimport type { ReactNode } from 'react';\nimport { cx } from '../utils/cx.js';\n\nexport interface RadioGroupProps {\n children: ReactNode;\n /** 선택된 값 (제어형). */\n value?: string;\n /** 초기 값 (비제어형). */\n defaultValue?: string;\n onValueChange?: (value: string) => void;\n /** 배치 방향 — 키보드 화살표 탐색 방향에도 반영됨. @default 'vertical' */\n orientation?: 'vertical' | 'horizontal';\n disabled?: boolean;\n /** 폼 전송용 name. */\n name?: string;\n required?: boolean;\n className?: string;\n /** 그룹의 접근 가능한 이름 (시각 제목이 없을 때 필수). */\n 'aria-label'?: string;\n 'aria-labelledby'?: string;\n}\n\n/**\n * 라디오 그룹. Radix RadioGroup 기반 — 화살표 키 그룹 탐색·단일 선택·폼 참여 자동.\n * 그룹에 `aria-label`(또는 aria-labelledby)을 지정한다.\n *\n * @example\n * <RadioGroup aria-label=\"정렬\" value={sort} onValueChange={setSort}>\n * <Radio value=\"latest\" label=\"최신순\" />\n * <Radio value=\"edited\" label=\"수정순\" />\n * </RadioGroup>\n */\nexport const RadioGroup = forwardRef<HTMLDivElement, RadioGroupProps>(function RadioGroup(\n { children, orientation = 'vertical', className, ...rest },\n ref,\n) {\n return (\n <RadixRadioGroup.Root\n ref={ref}\n orientation={orientation}\n className={cx('nuri-radio-group', `nuri-radio-group--${orientation}`, className)}\n {...rest}\n >\n {children}\n </RadixRadioGroup.Root>\n );\n});\n\nexport interface RadioProps {\n /** 이 항목이 나타내는 값. */\n value: string;\n /** 라벨 (컨트롤 우측). 없으면 `aria-label` 필수. */\n label?: ReactNode;\n disabled?: boolean;\n id?: string;\n className?: string;\n 'aria-label'?: string;\n}\n\n/** 라디오 항목 — 반드시 RadioGroup 안에서 사용. */\nexport const Radio = forwardRef<HTMLButtonElement, RadioProps>(function Radio(\n { value, label, disabled = false, id: idProp, className, ...rest },\n ref,\n) {\n const reactId = useId();\n const id = idProp ?? `nuri-radio-${reactId}`;\n\n return (\n <div className={cx('nuri-radio', className)}>\n <RadixRadioGroup.Item\n ref={ref}\n id={id}\n value={value}\n disabled={disabled}\n className=\"nuri-radio__control\"\n {...rest}\n >\n <RadixRadioGroup.Indicator className=\"nuri-radio__indicator\" />\n </RadixRadioGroup.Item>\n {label != null && (\n <label htmlFor={id} className=\"nuri-radio__label\">\n {label}\n </label>\n )}\n </div>\n );\n});\n","/**\n * 조건부 className 합성 유틸. falsy 값(undefined/false/null/'')은 제거.\n * @example cx('btn', isActive && 'btn--active', className)\n */\nexport function cx(...classes: Array<string | false | null | undefined>): string {\n return classes.filter(Boolean).join(' ');\n}\n"],"mappings":";AAEA,OAAO;AACP,YAAY,qBAAqB;AACjC,SAAS,YAAY,aAAa;;;ACA3B,SAAS,MAAM,SAA2D;AAC/E,SAAO,QAAQ,OAAO,OAAO,EAAE,KAAK,GAAG;AACzC;;;ADoCI,cA+BA,YA/BA;AALG,IAAM,aAAa,WAA4C,SAASA,YAC7E,EAAE,UAAU,cAAc,YAAY,WAAW,GAAG,KAAK,GACzD,KACA;AACA,SACE;AAAA,IAAiB;AAAA,IAAhB;AAAA,MACC;AAAA,MACA;AAAA,MACA,WAAW,GAAG,oBAAoB,qBAAqB,WAAW,IAAI,SAAS;AAAA,MAC9E,GAAG;AAAA,MAEH;AAAA;AAAA,EACH;AAEJ,CAAC;AAcM,IAAM,QAAQ,WAA0C,SAASC,OACtE,EAAE,OAAO,OAAO,WAAW,OAAO,IAAI,QAAQ,WAAW,GAAG,KAAK,GACjE,KACA;AACA,QAAM,UAAU,MAAM;AACtB,QAAM,KAAK,UAAU,cAAc,OAAO;AAE1C,SACE,qBAAC,SAAI,WAAW,GAAG,cAAc,SAAS,GACxC;AAAA;AAAA,MAAiB;AAAA,MAAhB;AAAA,QACC;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA,WAAU;AAAA,QACT,GAAG;AAAA,QAEJ,8BAAiB,2BAAhB,EAA0B,WAAU,yBAAwB;AAAA;AAAA,IAC/D;AAAA,IACC,SAAS,QACR,oBAAC,WAAM,SAAS,IAAI,WAAU,qBAC3B,iBACH;AAAA,KAEJ;AAEJ,CAAC;","names":["RadioGroup","Radio"]}