@nurimedia/react-ui 0.1.0 → 0.1.2
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.
- package/dist/AlertDialog/index.cjs +4 -180
- package/dist/AlertDialog/index.cjs.map +1 -1
- package/dist/AlertDialog/index.js +5 -144
- package/dist/AlertDialog/index.js.map +1 -1
- package/dist/Button/index.cjs +4 -131
- package/dist/Button/index.cjs.map +1 -1
- package/dist/Button/index.js +5 -105
- package/dist/Button/index.js.map +1 -1
- package/dist/Checkbox/index.cjs +4 -78
- package/dist/Checkbox/index.cjs.map +1 -1
- package/dist/Checkbox/index.js +5 -42
- package/dist/Checkbox/index.js.map +1 -1
- package/dist/Input/index.cjs +4 -98
- package/dist/Input/index.cjs.map +1 -1
- package/dist/Input/index.js +5 -72
- package/dist/Input/index.js.map +1 -1
- package/dist/Modal/index.cjs +4 -115
- package/dist/Modal/index.cjs.map +1 -1
- package/dist/Modal/index.js +5 -79
- package/dist/Modal/index.js.map +1 -1
- package/dist/Radio/index.cjs +6 -81
- package/dist/Radio/index.cjs.map +1 -1
- package/dist/Radio/index.js +6 -43
- package/dist/Radio/index.js.map +1 -1
- package/dist/Select/index.cjs +4 -111
- package/dist/Select/index.cjs.map +1 -1
- package/dist/Select/index.js +5 -75
- package/dist/Select/index.js.map +1 -1
- package/dist/Switch/index.cjs +4 -66
- package/dist/Switch/index.cjs.map +1 -1
- package/dist/Switch/index.js +5 -30
- package/dist/Switch/index.js.map +1 -1
- package/dist/Textarea/index.cjs +4 -122
- package/dist/Textarea/index.cjs.map +1 -1
- package/dist/Textarea/index.js +5 -96
- package/dist/Textarea/index.js.map +1 -1
- package/dist/Toast/index.cjs +4 -177
- package/dist/Toast/index.cjs.map +1 -1
- package/dist/Toast/index.js +6 -141
- package/dist/Toast/index.js.map +1 -1
- package/dist/Tooltip/index.cjs +6 -100
- package/dist/Tooltip/index.cjs.map +1 -1
- package/dist/Tooltip/index.js +6 -62
- package/dist/Tooltip/index.js.map +1 -1
- package/dist/chunk-2LFW4MM6.cjs +47 -0
- package/dist/chunk-2LFW4MM6.cjs.map +1 -0
- package/dist/chunk-2WKLA5RC.cjs +82 -0
- package/dist/chunk-2WKLA5RC.cjs.map +1 -0
- package/dist/chunk-2YGYLCCA.js +66 -0
- package/dist/chunk-2YGYLCCA.js.map +1 -0
- package/dist/chunk-323VGYMK.cjs +147 -0
- package/dist/chunk-323VGYMK.cjs.map +1 -0
- package/dist/chunk-3H47QMKS.js +47 -0
- package/dist/chunk-3H47QMKS.js.map +1 -0
- package/dist/chunk-3QSLHLC2.cjs +45 -0
- package/dist/chunk-3QSLHLC2.cjs.map +1 -0
- package/dist/chunk-4N5JIGIU.js +82 -0
- package/dist/chunk-4N5JIGIU.js.map +1 -0
- package/dist/chunk-DTGPVOJK.js +99 -0
- package/dist/chunk-DTGPVOJK.js.map +1 -0
- package/dist/chunk-DTQE42F7.js +33 -0
- package/dist/chunk-DTQE42F7.js.map +1 -0
- package/dist/chunk-DYO4PIBB.js +75 -0
- package/dist/chunk-DYO4PIBB.js.map +1 -0
- package/dist/chunk-GBMYANHH.js +149 -0
- package/dist/chunk-GBMYANHH.js.map +1 -0
- package/dist/chunk-I7KJ7WTN.cjs +149 -0
- package/dist/chunk-I7KJ7WTN.cjs.map +1 -0
- package/dist/chunk-JM4HDCLU.cjs +110 -0
- package/dist/chunk-JM4HDCLU.cjs.map +1 -0
- package/dist/chunk-JXJ6G634.cjs +75 -0
- package/dist/chunk-JXJ6G634.cjs.map +1 -0
- package/dist/chunk-LAPTEQ5N.js +45 -0
- package/dist/chunk-LAPTEQ5N.js.map +1 -0
- package/dist/chunk-LRATCHCB.cjs +66 -0
- package/dist/chunk-LRATCHCB.cjs.map +1 -0
- package/dist/chunk-OM4QMC4F.js +110 -0
- package/dist/chunk-OM4QMC4F.js.map +1 -0
- package/dist/chunk-PAXRFQUJ.cjs +33 -0
- package/dist/chunk-PAXRFQUJ.cjs.map +1 -0
- package/dist/chunk-PC3B67B4.cjs +78 -0
- package/dist/chunk-PC3B67B4.cjs.map +1 -0
- package/dist/chunk-PS6QXS5Z.js +11 -0
- package/dist/chunk-PS6QXS5Z.js.map +1 -0
- package/dist/chunk-Q26BJSLP.cjs +99 -0
- package/dist/chunk-Q26BJSLP.cjs.map +1 -0
- package/dist/chunk-R4E4BIPC.cjs +11 -0
- package/dist/chunk-R4E4BIPC.cjs.map +1 -0
- package/dist/chunk-R757N7RU.js +78 -0
- package/dist/chunk-R757N7RU.js.map +1 -0
- package/dist/chunk-RC773ZEY.js +147 -0
- package/dist/chunk-RC773ZEY.js.map +1 -0
- package/dist/index.cjs +54 -899
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +38 -833
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/utils/cx.ts"],"sourcesContent":["/**\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":";;;AAIO,SAAS,MAAM,SAA2D;AAC/E,SAAO,QAAQ,OAAO,OAAO,EAAE,KAAK,GAAG;AACzC;","names":[]}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
"use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; }'use client';
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
var _chunkR4E4BIPCcjs = require('./chunk-R4E4BIPC.cjs');
|
|
5
|
+
|
|
6
|
+
// src/Textarea/Textarea.tsx
|
|
7
|
+
require('./Textarea/styles.css');
|
|
8
|
+
var _react = require('react');
|
|
9
|
+
var _jsxruntime = require('react/jsx-runtime');
|
|
10
|
+
var Textarea = _react.forwardRef.call(void 0, function Textarea2({
|
|
11
|
+
label,
|
|
12
|
+
hideLabel = false,
|
|
13
|
+
description,
|
|
14
|
+
invalid = false,
|
|
15
|
+
errorMessage,
|
|
16
|
+
block = false,
|
|
17
|
+
resize = "vertical",
|
|
18
|
+
autosize = false,
|
|
19
|
+
maxRows,
|
|
20
|
+
rows = 3,
|
|
21
|
+
disabled = false,
|
|
22
|
+
className,
|
|
23
|
+
id: idProp,
|
|
24
|
+
value,
|
|
25
|
+
onChange,
|
|
26
|
+
"aria-describedby": ariaDescribedby,
|
|
27
|
+
...rest
|
|
28
|
+
}, ref) {
|
|
29
|
+
const reactId = _react.useId.call(void 0, );
|
|
30
|
+
const id = _nullishCoalesce(idProp, () => ( `nuri-textarea-${reactId}`));
|
|
31
|
+
const messageId = `${id}-message`;
|
|
32
|
+
const innerRef = _react.useRef.call(void 0, null);
|
|
33
|
+
const setRefs = _react.useCallback.call(void 0,
|
|
34
|
+
(node) => {
|
|
35
|
+
innerRef.current = node;
|
|
36
|
+
if (typeof ref === "function") ref(node);
|
|
37
|
+
else if (ref) ref.current = node;
|
|
38
|
+
},
|
|
39
|
+
[ref]
|
|
40
|
+
);
|
|
41
|
+
const fitHeight = _react.useCallback.call(void 0, () => {
|
|
42
|
+
const el = innerRef.current;
|
|
43
|
+
if (!el) return;
|
|
44
|
+
const cs = getComputedStyle(el);
|
|
45
|
+
const borders = parseFloat(cs.borderTopWidth) + parseFloat(cs.borderBottomWidth);
|
|
46
|
+
const lineHeight = parseFloat(cs.lineHeight);
|
|
47
|
+
const maxHeight = maxRows != null && !Number.isNaN(lineHeight) ? maxRows * lineHeight + parseFloat(cs.paddingTop) + parseFloat(cs.paddingBottom) + borders : Infinity;
|
|
48
|
+
el.style.height = "auto";
|
|
49
|
+
const contentHeight = el.scrollHeight + borders;
|
|
50
|
+
const next = Math.min(contentHeight, maxHeight);
|
|
51
|
+
if (!Number.isNaN(next)) el.style.height = `${next}px`;
|
|
52
|
+
el.style.overflowY = contentHeight > maxHeight ? "auto" : "hidden";
|
|
53
|
+
}, [maxRows]);
|
|
54
|
+
_react.useLayoutEffect.call(void 0, () => {
|
|
55
|
+
if (autosize) fitHeight();
|
|
56
|
+
}, [autosize, fitHeight, value]);
|
|
57
|
+
const handleChange = (e) => {
|
|
58
|
+
_optionalChain([onChange, 'optionalCall', _ => _(e)]);
|
|
59
|
+
if (autosize) fitHeight();
|
|
60
|
+
};
|
|
61
|
+
const message = invalid && errorMessage != null ? errorMessage : description;
|
|
62
|
+
const hasMessage = message != null;
|
|
63
|
+
const describedBy = _chunkR4E4BIPCcjs.cx.call(void 0, hasMessage ? messageId : void 0, ariaDescribedby) || void 0;
|
|
64
|
+
return /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: _chunkR4E4BIPCcjs.cx.call(void 0, "nuri-textarea", block && "nuri-textarea--block", className), children: [
|
|
65
|
+
label != null && /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "label", { htmlFor: id, className: _chunkR4E4BIPCcjs.cx.call(void 0, "nuri-textarea__label", hideLabel && "nuri-sr-only"), children: label }),
|
|
66
|
+
/* @__PURE__ */ _jsxruntime.jsx.call(void 0,
|
|
67
|
+
"textarea",
|
|
68
|
+
{
|
|
69
|
+
ref: setRefs,
|
|
70
|
+
id,
|
|
71
|
+
rows,
|
|
72
|
+
value,
|
|
73
|
+
onChange: handleChange,
|
|
74
|
+
className: _chunkR4E4BIPCcjs.cx.call(void 0,
|
|
75
|
+
"nuri-textarea__control",
|
|
76
|
+
invalid && "nuri-textarea__control--invalid",
|
|
77
|
+
(resize === "none" || autosize) && "nuri-textarea__control--resize-none"
|
|
78
|
+
),
|
|
79
|
+
disabled,
|
|
80
|
+
"aria-invalid": invalid || void 0,
|
|
81
|
+
"aria-describedby": describedBy,
|
|
82
|
+
...rest
|
|
83
|
+
}
|
|
84
|
+
),
|
|
85
|
+
hasMessage && /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
|
|
86
|
+
"p",
|
|
87
|
+
{
|
|
88
|
+
id: messageId,
|
|
89
|
+
className: _chunkR4E4BIPCcjs.cx.call(void 0, "nuri-textarea__message", invalid && "nuri-textarea__message--error"),
|
|
90
|
+
children: message
|
|
91
|
+
}
|
|
92
|
+
)
|
|
93
|
+
] });
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
exports.Textarea = Textarea;
|
|
99
|
+
//# sourceMappingURL=chunk-Q26BJSLP.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["/Users/summerhoony/Desktop/projects/nuri-ui/dist/chunk-Q26BJSLP.cjs","../src/Textarea/Textarea.tsx"],"names":["Textarea"],"mappings":"AAAA,6rBAAY;AACZ;AACE;AACF,wDAA6B;AAC7B;AACA;ACHA,iCAAO;AACP,8BAAwE;AAwHpE,+CAAA;AAzEG,IAAM,SAAA,EAAW,+BAAA,SAAwDA,SAAAA,CAC9E;AAAA,EACE,KAAA;AAAA,EACA,UAAA,EAAY,KAAA;AAAA,EACZ,WAAA;AAAA,EACA,QAAA,EAAU,KAAA;AAAA,EACV,YAAA;AAAA,EACA,MAAA,EAAQ,KAAA;AAAA,EACR,OAAA,EAAS,UAAA;AAAA,EACT,SAAA,EAAW,KAAA;AAAA,EACX,OAAA;AAAA,EACA,KAAA,EAAO,CAAA;AAAA,EACP,SAAA,EAAW,KAAA;AAAA,EACX,SAAA;AAAA,EACA,EAAA,EAAI,MAAA;AAAA,EACJ,KAAA;AAAA,EACA,QAAA;AAAA,EACA,kBAAA,EAAoB,eAAA;AAAA,EACpB,GAAG;AACL,CAAA,EACA,GAAA,EACA;AACA,EAAA,MAAM,QAAA,EAAU,0BAAA,CAAM;AACtB,EAAA,MAAM,GAAA,mBAAK,MAAA,UAAU,CAAA,cAAA,EAAiB,OAAO,CAAA,GAAA;AACtB,EAAA;AAEiC,EAAA;AACxC,EAAA;AACwB,IAAA;AACjB,MAAA;AACoB,MAAA;AACX,MAAA;AAC9B,IAAA;AACI,IAAA;AACN,EAAA;AAGoC,EAAA;AACd,IAAA;AACX,IAAA;AACqB,IAAA;AACc,IAAA;AACD,IAAA;AAGrB,IAAA;AAIJ,IAAA;AACsB,IAAA;AACH,IAAA;AACM,IAAA;AACN,IAAA;AAC3B,EAAA;AAGU,EAAA;AACI,IAAA;AACK,EAAA;AAE+B,EAAA;AAChD,oBAAA;AACY,IAAA;AAC1B,EAAA;AAG2C,EAAA;AACb,EAAA;AAEZ,EAAA;AAGX,EAAA;AAEA,IAAA;AAKH,oBAAA;AAAC,MAAA;AAAA,MAAA;AACM,QAAA;AACL,QAAA;AACA,QAAA;AACA,QAAA;AACU,QAAA;AACC,QAAA;AACT,UAAA;AACW,UAAA;AACwB,UAAA;AACrC,QAAA;AACA,QAAA;AACyB,QAAA;AACP,QAAA;AACd,QAAA;AAAA,MAAA;AACN,IAAA;AAGE,IAAA;AAAC,MAAA;AAAA,MAAA;AACK,QAAA;AACoC,QAAA;AAEvC,QAAA;AAAA,MAAA;AACH,IAAA;AAEJ,EAAA;AAEH;AD/D+C;AACA;AACA;AACA","file":"/Users/summerhoony/Desktop/projects/nuri-ui/dist/chunk-Q26BJSLP.cjs","sourcesContent":[null,"'use client';\n\nimport './styles.css';\nimport { forwardRef, useCallback, useId, useLayoutEffect, useRef } from 'react';\nimport type { ChangeEvent, TextareaHTMLAttributes, ReactNode } from 'react';\nimport { cx } from '../utils/cx.js';\n\nexport interface TextareaProps extends TextareaHTMLAttributes<HTMLTextAreaElement> {\n /**\n * 라벨 (필드 위). 지정 시 `<label>`로 렌더되어 자동 연결됨.\n * 라벨을 두지 않으면 `aria-label`로 접근 가능한 이름을 주세요.\n */\n label?: ReactNode;\n /** 라벨을 시각적으로만 숨김 (스크린리더에는 노출). @default false */\n hideLabel?: boolean;\n /** 평상시 힌트 (회색, 필드 아래). `aria-describedby`로 연결됨. */\n description?: ReactNode;\n /** 에러 상태 — 보더·텍스트·배경이 에러색으로 바뀌고 `aria-invalid`가 설정됨. */\n invalid?: boolean;\n /** `invalid`일 때 표시할 에러 메시지 (빨강). 있으면 description을 대체. */\n errorMessage?: ReactNode;\n /** 컨테이너 전체 너비로 확장. @default false */\n block?: boolean;\n /**\n * 크기 조절 핸들. 세로 조절 허용이 기본 — 긴 입력을 사용자가 늘려 볼 수 있게.\n * `autosize`가 켜지면 무시된다(자동 크기와 수동 핸들은 충돌).\n * @default 'vertical'\n */\n resize?: 'vertical' | 'none';\n /**\n * 내용에 따라 높이 자동 조절. `rows`(최소)에서 시작해 내용만큼 늘어나고,\n * `maxRows`에 도달하면 멈추고 스크롤이 생긴다.\n * @default false\n */\n autosize?: boolean;\n /** `autosize`일 때 최대 줄 수 — 넘으면 스크롤. 미지정 시 무제한 확장. */\n maxRows?: number;\n}\n\n/**\n * 여러 줄 텍스트 입력. Input과 동일한 디자인 언어(보더·상태·타이포)와\n * 라벨/힌트/에러 자동 연결 패턴을 따르는 자체 완결형.\n * 높이는 native `rows`(기본 3)로 제어하고, `autosize`로 자동 확장도 지원한다.\n *\n * @example\n * <Textarea label=\"문의 내용\" rows={5} block />\n *\n * @example 자동 높이 — 3줄에서 시작해 8줄까지 늘어난 뒤 스크롤\n * <Textarea label=\"댓글\" autosize rows={3} maxRows={8} />\n */\nexport const Textarea = forwardRef<HTMLTextAreaElement, TextareaProps>(function Textarea(\n {\n label,\n hideLabel = false,\n description,\n invalid = false,\n errorMessage,\n block = false,\n resize = 'vertical',\n autosize = false,\n maxRows,\n rows = 3,\n disabled = false,\n className,\n id: idProp,\n value,\n onChange,\n 'aria-describedby': ariaDescribedby,\n ...rest\n },\n ref,\n) {\n const reactId = useId();\n const id = idProp ?? `nuri-textarea-${reactId}`;\n const messageId = `${id}-message`;\n\n const innerRef = useRef<HTMLTextAreaElement | null>(null);\n const setRefs = useCallback(\n (node: HTMLTextAreaElement | null) => {\n innerRef.current = node;\n if (typeof ref === 'function') ref(node);\n else if (ref) ref.current = node;\n },\n [ref],\n );\n\n // autosize: height를 내용(scrollHeight)에 맞추되 maxRows 상한에서 클램프.\n const fitHeight = useCallback(() => {\n const el = innerRef.current;\n if (!el) return;\n const cs = getComputedStyle(el);\n const borders = parseFloat(cs.borderTopWidth) + parseFloat(cs.borderBottomWidth);\n const lineHeight = parseFloat(cs.lineHeight);\n // maxRows → px 상한 (line-height 계산 불가 환경에서는 무제한)\n const maxHeight =\n maxRows != null && !Number.isNaN(lineHeight)\n ? maxRows * lineHeight + parseFloat(cs.paddingTop) + parseFloat(cs.paddingBottom) + borders\n : Infinity;\n\n el.style.height = 'auto'; // 줄어드는 경우도 반영되도록 초기화 후 측정\n const contentHeight = el.scrollHeight + borders; // scrollHeight는 border 미포함(border-box 보정)\n const next = Math.min(contentHeight, maxHeight);\n if (!Number.isNaN(next)) el.style.height = `${next}px`;\n el.style.overflowY = contentHeight > maxHeight ? 'auto' : 'hidden';\n }, [maxRows]);\n\n // 마운트·제어형 value 변경·autosize 토글 시 재계산\n useLayoutEffect(() => {\n if (autosize) fitHeight();\n }, [autosize, fitHeight, value]);\n\n const handleChange = (e: ChangeEvent<HTMLTextAreaElement>) => {\n onChange?.(e);\n if (autosize) fitHeight(); // 비제어형 입력에도 반응\n };\n\n // invalid + errorMessage → 에러 메시지 / 아니면 힌트. 시각 슬롯은 항상 한 줄. (Input과 동일)\n const message = invalid && errorMessage != null ? errorMessage : description;\n const hasMessage = message != null;\n const describedBy =\n cx(hasMessage ? messageId : undefined, ariaDescribedby as string | undefined) || undefined;\n\n return (\n <div className={cx('nuri-textarea', block && 'nuri-textarea--block', className)}>\n {label != null && (\n <label htmlFor={id} className={cx('nuri-textarea__label', hideLabel && 'nuri-sr-only')}>\n {label}\n </label>\n )}\n\n <textarea\n ref={setRefs}\n id={id}\n rows={rows}\n value={value}\n onChange={handleChange}\n className={cx(\n 'nuri-textarea__control',\n invalid && 'nuri-textarea__control--invalid',\n (resize === 'none' || autosize) && 'nuri-textarea__control--resize-none',\n )}\n disabled={disabled}\n aria-invalid={invalid || undefined}\n aria-describedby={describedBy}\n {...rest}\n />\n\n {hasMessage && (\n <p\n id={messageId}\n className={cx('nuri-textarea__message', invalid && 'nuri-textarea__message--error')}\n >\n {message}\n </p>\n )}\n </div>\n );\n});\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["/Users/summerhoony/Desktop/projects/nuri-ui/dist/chunk-R4E4BIPC.cjs","../src/utils/cx.ts"],"names":[],"mappings":"AAAA,qFAAY;AACZ;AACA;ACEO,SAAS,EAAA,CAAA,GAAM,OAAA,EAA2D;AAC/E,EAAA,OAAO,OAAA,CAAQ,MAAA,CAAO,OAAO,CAAA,CAAE,IAAA,CAAK,GAAG,CAAA;AACzC;ADAA;AACA;AACE;AACF,gBAAC","file":"/Users/summerhoony/Desktop/projects/nuri-ui/dist/chunk-R4E4BIPC.cjs","sourcesContent":[null,"/**\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"]}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import {
|
|
3
|
+
cx
|
|
4
|
+
} from "./chunk-PS6QXS5Z.js";
|
|
5
|
+
|
|
6
|
+
// src/Select/Select.tsx
|
|
7
|
+
import "./Select/styles.css";
|
|
8
|
+
import * as RadixSelect from "@radix-ui/react-select";
|
|
9
|
+
import { forwardRef } from "react";
|
|
10
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
11
|
+
var CaretIcon = () => /* @__PURE__ */ jsx("svg", { width: "8", height: "8", viewBox: "0 0 8 8", fill: "none", "aria-hidden": "true", children: /* @__PURE__ */ jsx("path", { d: "M1.1 2.5h5.8L4 5.9z", fill: "currentColor" }) });
|
|
12
|
+
var Select = forwardRef(function Select2({
|
|
13
|
+
options,
|
|
14
|
+
size = "md",
|
|
15
|
+
placeholder,
|
|
16
|
+
value,
|
|
17
|
+
defaultValue,
|
|
18
|
+
onValueChange,
|
|
19
|
+
open,
|
|
20
|
+
defaultOpen,
|
|
21
|
+
onOpenChange,
|
|
22
|
+
disabled = false,
|
|
23
|
+
block = false,
|
|
24
|
+
name,
|
|
25
|
+
required,
|
|
26
|
+
container,
|
|
27
|
+
className,
|
|
28
|
+
"aria-label": ariaLabel
|
|
29
|
+
}, ref) {
|
|
30
|
+
return /* @__PURE__ */ jsxs(
|
|
31
|
+
RadixSelect.Root,
|
|
32
|
+
{
|
|
33
|
+
value,
|
|
34
|
+
defaultValue,
|
|
35
|
+
onValueChange,
|
|
36
|
+
open,
|
|
37
|
+
defaultOpen,
|
|
38
|
+
onOpenChange,
|
|
39
|
+
disabled,
|
|
40
|
+
name,
|
|
41
|
+
required,
|
|
42
|
+
children: [
|
|
43
|
+
/* @__PURE__ */ jsxs(
|
|
44
|
+
RadixSelect.Trigger,
|
|
45
|
+
{
|
|
46
|
+
ref,
|
|
47
|
+
"aria-label": ariaLabel,
|
|
48
|
+
className: cx(
|
|
49
|
+
"nuri-select__trigger",
|
|
50
|
+
`nuri-select__trigger--${size}`,
|
|
51
|
+
block && "nuri-select__trigger--block",
|
|
52
|
+
className
|
|
53
|
+
),
|
|
54
|
+
children: [
|
|
55
|
+
/* @__PURE__ */ jsx("span", { className: "nuri-select__value", children: /* @__PURE__ */ jsx(RadixSelect.Value, { placeholder }) }),
|
|
56
|
+
/* @__PURE__ */ jsx(RadixSelect.Icon, { className: "nuri-select__caret", children: /* @__PURE__ */ jsx(CaretIcon, {}) })
|
|
57
|
+
]
|
|
58
|
+
}
|
|
59
|
+
),
|
|
60
|
+
/* @__PURE__ */ jsx(RadixSelect.Portal, { container, children: /* @__PURE__ */ jsx(RadixSelect.Content, { className: "nuri-select__content", position: "popper", sideOffset: 6, children: /* @__PURE__ */ jsx(RadixSelect.Viewport, { className: "nuri-select__viewport", children: options.map((option) => /* @__PURE__ */ jsx(
|
|
61
|
+
RadixSelect.Item,
|
|
62
|
+
{
|
|
63
|
+
value: option.value,
|
|
64
|
+
disabled: option.disabled,
|
|
65
|
+
className: "nuri-select__item",
|
|
66
|
+
children: /* @__PURE__ */ jsx(RadixSelect.ItemText, { children: option.label })
|
|
67
|
+
},
|
|
68
|
+
option.value
|
|
69
|
+
)) }) }) })
|
|
70
|
+
]
|
|
71
|
+
}
|
|
72
|
+
);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
export {
|
|
76
|
+
Select
|
|
77
|
+
};
|
|
78
|
+
//# sourceMappingURL=chunk-R757N7RU.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/Select/Select.tsx"],"sourcesContent":["'use client';\n\nimport './styles.css';\nimport * as RadixSelect from '@radix-ui/react-select';\nimport { forwardRef } from 'react';\nimport type { ReactNode } from 'react';\nimport { cx } from '../utils/cx.js';\n\ntype Size = 'sm' | 'md';\n\nexport interface SelectOption {\n value: string;\n label: ReactNode;\n disabled?: boolean;\n}\n\nexport interface SelectProps {\n /** 선택지 목록. */\n options: SelectOption[];\n /**\n * 사이즈. sm 36 / md 42 px 높이 (디자인 시스템의 Small/Large).\n * @default 'md'\n */\n size?: Size;\n /** 값이 없을 때 트리거에 표시할 문구. */\n placeholder?: ReactNode;\n /** 선택된 값 (제어형). */\n value?: string;\n /** 초기 값 (비제어형). */\n defaultValue?: string;\n onValueChange?: (value: string) => void;\n /** 열림 상태 제어 (선택). */\n open?: boolean;\n defaultOpen?: boolean;\n onOpenChange?: (open: boolean) => void;\n disabled?: boolean;\n /** 컨테이너 전체 너비로 확장. @default false */\n block?: boolean;\n /** 폼 전송용 name — 네이티브 select처럼 폼에 참여. */\n name?: string;\n required?: boolean;\n /** 포털 렌더 대상. 기본 document.body. */\n container?: HTMLElement | null;\n /** 트리거에 추가할 className. */\n className?: string;\n /** 시각 라벨이 없을 때 접근 가능한 이름. */\n 'aria-label'?: string;\n}\n\n/** 디자인 시스템의 8px 삼각 캐럿 (열림 시 CSS로 회전). */\nconst CaretIcon = () => (\n <svg width=\"8\" height=\"8\" viewBox=\"0 0 8 8\" fill=\"none\" aria-hidden=\"true\">\n <path d=\"M1.1 2.5h5.8L4 5.9z\" fill=\"currentColor\" />\n </svg>\n);\n\n/**\n * 셀렉트 박스 (단일 선택). Radix Select 기반 — 키보드 탐색·타입어헤드·\n * 포지셔닝·aria 연결을 자동 제공. 옵션 목록은 `options` 배열로 전달한다.\n *\n * 다중 선택(체크박스 옵션)·서브메뉴 변형은 별도 컴포넌트(DropdownMenu) 범위.\n *\n * @example\n * <Select\n * aria-label=\"정렬\"\n * placeholder=\"정렬 선택\"\n * options={[\n * { value: 'latest', label: '최신순' },\n * { value: 'edited', label: '수정순' },\n * ]}\n * onValueChange={setSort}\n * />\n */\nexport const Select = forwardRef<HTMLButtonElement, SelectProps>(function Select(\n {\n options,\n size = 'md',\n placeholder,\n value,\n defaultValue,\n onValueChange,\n open,\n defaultOpen,\n onOpenChange,\n disabled = false,\n block = false,\n name,\n required,\n container,\n className,\n 'aria-label': ariaLabel,\n },\n ref,\n) {\n return (\n <RadixSelect.Root\n value={value}\n defaultValue={defaultValue}\n onValueChange={onValueChange}\n open={open}\n defaultOpen={defaultOpen}\n onOpenChange={onOpenChange}\n disabled={disabled}\n name={name}\n required={required}\n >\n <RadixSelect.Trigger\n ref={ref}\n aria-label={ariaLabel}\n className={cx(\n 'nuri-select__trigger',\n `nuri-select__trigger--${size}`,\n block && 'nuri-select__trigger--block',\n className,\n )}\n >\n <span className=\"nuri-select__value\">\n <RadixSelect.Value placeholder={placeholder} />\n </span>\n <RadixSelect.Icon className=\"nuri-select__caret\">\n <CaretIcon />\n </RadixSelect.Icon>\n </RadixSelect.Trigger>\n\n <RadixSelect.Portal container={container}>\n <RadixSelect.Content className=\"nuri-select__content\" position=\"popper\" sideOffset={6}>\n <RadixSelect.Viewport className=\"nuri-select__viewport\">\n {options.map((option) => (\n <RadixSelect.Item\n key={option.value}\n value={option.value}\n disabled={option.disabled}\n className=\"nuri-select__item\"\n >\n <RadixSelect.ItemText>{option.label}</RadixSelect.ItemText>\n </RadixSelect.Item>\n ))}\n </RadixSelect.Viewport>\n </RadixSelect.Content>\n </RadixSelect.Portal>\n </RadixSelect.Root>\n );\n});\n"],"mappings":";;;;;;AAEA,OAAO;AACP,YAAY,iBAAiB;AAC7B,SAAS,kBAAkB;AAgDvB,cAsDE,YAtDF;AAFJ,IAAM,YAAY,MAChB,oBAAC,SAAI,OAAM,KAAI,QAAO,KAAI,SAAQ,WAAU,MAAK,QAAO,eAAY,QAClE,8BAAC,UAAK,GAAE,uBAAsB,MAAK,gBAAe,GACpD;AAoBK,IAAM,SAAS,WAA2C,SAASA,QACxE;AAAA,EACE;AAAA,EACA,OAAO;AAAA,EACP;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,WAAW;AAAA,EACX,QAAQ;AAAA,EACR;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,cAAc;AAChB,GACA,KACA;AACA,SACE;AAAA,IAAa;AAAA,IAAZ;AAAA,MACC;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MAEA;AAAA;AAAA,UAAa;AAAA,UAAZ;AAAA,YACC;AAAA,YACA,cAAY;AAAA,YACZ,WAAW;AAAA,cACT;AAAA,cACA,yBAAyB,IAAI;AAAA,cAC7B,SAAS;AAAA,cACT;AAAA,YACF;AAAA,YAEA;AAAA,kCAAC,UAAK,WAAU,sBACd,8BAAa,mBAAZ,EAAkB,aAA0B,GAC/C;AAAA,cACA,oBAAa,kBAAZ,EAAiB,WAAU,sBAC1B,8BAAC,aAAU,GACb;AAAA;AAAA;AAAA,QACF;AAAA,QAEA,oBAAa,oBAAZ,EAAmB,WAClB,8BAAa,qBAAZ,EAAoB,WAAU,wBAAuB,UAAS,UAAS,YAAY,GAClF,8BAAa,sBAAZ,EAAqB,WAAU,yBAC7B,kBAAQ,IAAI,CAAC,WACZ;AAAA,UAAa;AAAA,UAAZ;AAAA,YAEC,OAAO,OAAO;AAAA,YACd,UAAU,OAAO;AAAA,YACjB,WAAU;AAAA,YAEV,8BAAa,sBAAZ,EAAsB,iBAAO,OAAM;AAAA;AAAA,UAL/B,OAAO;AAAA,QAMd,CACD,GACH,GACF,GACF;AAAA;AAAA;AAAA,EACF;AAEJ,CAAC;","names":["Select"]}
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import {
|
|
3
|
+
cx
|
|
4
|
+
} from "./chunk-PS6QXS5Z.js";
|
|
5
|
+
|
|
6
|
+
// src/Toast/Toaster.tsx
|
|
7
|
+
import "./Toast/styles.css";
|
|
8
|
+
import * as RadixToast from "@radix-ui/react-toast";
|
|
9
|
+
|
|
10
|
+
// src/Toast/store.ts
|
|
11
|
+
import { useSyncExternalStore } from "react";
|
|
12
|
+
var toasts = [];
|
|
13
|
+
var listeners = /* @__PURE__ */ new Set();
|
|
14
|
+
var EMPTY = [];
|
|
15
|
+
var counter = 0;
|
|
16
|
+
var emit = () => {
|
|
17
|
+
for (const l of listeners) l();
|
|
18
|
+
};
|
|
19
|
+
function add(message, options = {}) {
|
|
20
|
+
const id = options.id ?? `nuri-toast-${++counter}`;
|
|
21
|
+
const record = {
|
|
22
|
+
id,
|
|
23
|
+
message,
|
|
24
|
+
tone: options.tone ?? "default",
|
|
25
|
+
duration: options.duration ?? 3e3
|
|
26
|
+
};
|
|
27
|
+
const idx = toasts.findIndex((t) => t.id === id);
|
|
28
|
+
toasts = idx >= 0 ? toasts.map((t) => t.id === id ? record : t) : [...toasts, record];
|
|
29
|
+
emit();
|
|
30
|
+
return id;
|
|
31
|
+
}
|
|
32
|
+
function remove(id) {
|
|
33
|
+
toasts = toasts.filter((t) => t.id !== id);
|
|
34
|
+
emit();
|
|
35
|
+
}
|
|
36
|
+
function clear() {
|
|
37
|
+
toasts = [];
|
|
38
|
+
emit();
|
|
39
|
+
}
|
|
40
|
+
var toastStore = {
|
|
41
|
+
add,
|
|
42
|
+
remove,
|
|
43
|
+
clear,
|
|
44
|
+
subscribe(listener) {
|
|
45
|
+
listeners.add(listener);
|
|
46
|
+
return () => {
|
|
47
|
+
listeners.delete(listener);
|
|
48
|
+
};
|
|
49
|
+
},
|
|
50
|
+
getSnapshot: () => toasts,
|
|
51
|
+
getServerSnapshot: () => EMPTY
|
|
52
|
+
};
|
|
53
|
+
function useToasts() {
|
|
54
|
+
return useSyncExternalStore(
|
|
55
|
+
toastStore.subscribe,
|
|
56
|
+
toastStore.getSnapshot,
|
|
57
|
+
toastStore.getServerSnapshot
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// src/Toast/icons.tsx
|
|
62
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
63
|
+
var CheckIcon = () => /* @__PURE__ */ jsx("svg", { width: "16", height: "16", viewBox: "0 0 16 16", fill: "none", "aria-hidden": "true", children: /* @__PURE__ */ jsx(
|
|
64
|
+
"path",
|
|
65
|
+
{
|
|
66
|
+
d: "M3.5 8.5l3 3 6-6.5",
|
|
67
|
+
stroke: "currentColor",
|
|
68
|
+
strokeWidth: "1.6",
|
|
69
|
+
strokeLinecap: "round",
|
|
70
|
+
strokeLinejoin: "round"
|
|
71
|
+
}
|
|
72
|
+
) });
|
|
73
|
+
var ErrorIcon = () => /* @__PURE__ */ jsxs("svg", { width: "16", height: "16", viewBox: "0 0 16 16", fill: "none", "aria-hidden": "true", children: [
|
|
74
|
+
/* @__PURE__ */ jsx("path", { d: "M8 4v4.5", stroke: "currentColor", strokeWidth: "1.6", strokeLinecap: "round" }),
|
|
75
|
+
/* @__PURE__ */ jsx("circle", { cx: "8", cy: "11.3", r: "0.95", fill: "currentColor" })
|
|
76
|
+
] });
|
|
77
|
+
var InfoIcon = () => /* @__PURE__ */ jsxs("svg", { width: "16", height: "16", viewBox: "0 0 16 16", fill: "none", "aria-hidden": "true", children: [
|
|
78
|
+
/* @__PURE__ */ jsx("path", { d: "M8 7.5v4", stroke: "currentColor", strokeWidth: "1.6", strokeLinecap: "round" }),
|
|
79
|
+
/* @__PURE__ */ jsx("circle", { cx: "8", cy: "4.7", r: "0.95", fill: "currentColor" })
|
|
80
|
+
] });
|
|
81
|
+
var toneIcon = {
|
|
82
|
+
success: /* @__PURE__ */ jsx(CheckIcon, {}),
|
|
83
|
+
error: /* @__PURE__ */ jsx(ErrorIcon, {}),
|
|
84
|
+
info: /* @__PURE__ */ jsx(InfoIcon, {})
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
// src/Toast/Toaster.tsx
|
|
88
|
+
import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
89
|
+
function Toaster({
|
|
90
|
+
swipeDirection = "down",
|
|
91
|
+
swipeThreshold = 50,
|
|
92
|
+
className,
|
|
93
|
+
label = "\uC54C\uB9BC"
|
|
94
|
+
}) {
|
|
95
|
+
const toasts2 = useToasts();
|
|
96
|
+
return /* @__PURE__ */ jsxs2(
|
|
97
|
+
RadixToast.Provider,
|
|
98
|
+
{
|
|
99
|
+
swipeDirection,
|
|
100
|
+
swipeThreshold,
|
|
101
|
+
label,
|
|
102
|
+
children: [
|
|
103
|
+
toasts2.map((t) => /* @__PURE__ */ jsxs2(
|
|
104
|
+
RadixToast.Root,
|
|
105
|
+
{
|
|
106
|
+
duration: t.duration,
|
|
107
|
+
onOpenChange: (open) => {
|
|
108
|
+
if (!open) toastStore.remove(t.id);
|
|
109
|
+
},
|
|
110
|
+
type: t.tone === "error" ? "foreground" : "background",
|
|
111
|
+
className: cx("nuri-toast", t.tone !== "default" && `nuri-toast--${t.tone}`),
|
|
112
|
+
children: [
|
|
113
|
+
t.tone !== "default" && /* @__PURE__ */ jsx2("span", { className: "nuri-toast__icon", "aria-hidden": "true", children: toneIcon[t.tone] }),
|
|
114
|
+
/* @__PURE__ */ jsx2(RadixToast.Title, { className: "nuri-toast__message", children: t.message })
|
|
115
|
+
]
|
|
116
|
+
},
|
|
117
|
+
t.id
|
|
118
|
+
)),
|
|
119
|
+
/* @__PURE__ */ jsx2(
|
|
120
|
+
RadixToast.Viewport,
|
|
121
|
+
{
|
|
122
|
+
className: cx("nuri-toast__viewport", className),
|
|
123
|
+
label: `${label} ({hotkey})`
|
|
124
|
+
}
|
|
125
|
+
)
|
|
126
|
+
]
|
|
127
|
+
}
|
|
128
|
+
);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// src/Toast/toast.ts
|
|
132
|
+
function toastFn(message, options) {
|
|
133
|
+
return toastStore.add(message, options);
|
|
134
|
+
}
|
|
135
|
+
var toast = Object.assign(toastFn, {
|
|
136
|
+
success: (message, options) => toastStore.add(message, { ...options, tone: "success" }),
|
|
137
|
+
error: (message, options) => toastStore.add(message, { ...options, tone: "error" }),
|
|
138
|
+
info: (message, options) => toastStore.add(message, { ...options, tone: "info" }),
|
|
139
|
+
/** id 지정 시 해당 토스트만, 없으면 전체 제거. */
|
|
140
|
+
dismiss: (id) => id ? toastStore.remove(id) : toastStore.clear()
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
export {
|
|
144
|
+
Toaster,
|
|
145
|
+
toast
|
|
146
|
+
};
|
|
147
|
+
//# sourceMappingURL=chunk-RC773ZEY.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/Toast/Toaster.tsx","../src/Toast/store.ts","../src/Toast/icons.tsx","../src/Toast/toast.ts"],"sourcesContent":["'use client';\n\nimport './styles.css';\nimport * as RadixToast from '@radix-ui/react-toast';\nimport { cx } from '../utils/cx.js';\nimport { toastStore, useToasts } from './store.js';\nimport { toneIcon } from './icons.js';\n\nexport interface ToasterProps {\n /**\n * 스와이프로 닫는 방향. 하단 표시라 기본 `down`(아래로 밀어 닫기 — 터치·드래그 모두).\n * @default 'down'\n */\n swipeDirection?: RadixToast.ToastProviderProps['swipeDirection'];\n /** 스와이프 임계 거리(px). @default 50 */\n swipeThreshold?: number;\n /** 뷰포트에 추가할 className (위치 커스터마이즈 등). */\n className?: string;\n /** 스크린리더용 지역 라벨. @default '알림' */\n label?: string;\n}\n\n/**\n * 토스트 렌더러. **앱 루트에 1회만** 마운트하면 되고, 표시는 `toast()` 호출로 트리거한다.\n * (사용처마다 렌더할 필요 없음 — 모듈 store를 구독해 이 컴포넌트만 렌더)\n *\n * Radix Toast 기반이라 자동 닫힘 타이머·hover 시 일시정지·스와이프 닫기·\n * 스크린리더 announce(aria-live)를 표준으로 제공한다.\n *\n * @example\n * // app/layout.tsx\n * <body>{children}<Toaster /></body>\n */\nexport function Toaster({\n swipeDirection = 'down',\n swipeThreshold = 50,\n className,\n label = '알림',\n}: ToasterProps) {\n const toasts = useToasts();\n\n return (\n <RadixToast.Provider\n swipeDirection={swipeDirection}\n swipeThreshold={swipeThreshold}\n label={label}\n >\n {toasts.map((t) => (\n <RadixToast.Root\n key={t.id}\n duration={t.duration}\n onOpenChange={(open) => {\n if (!open) toastStore.remove(t.id);\n }}\n // error는 foreground(assertive)로 즉시 안내, 그 외는 background(polite)\n type={t.tone === 'error' ? 'foreground' : 'background'}\n className={cx('nuri-toast', t.tone !== 'default' && `nuri-toast--${t.tone}`)}\n >\n {t.tone !== 'default' && (\n <span className=\"nuri-toast__icon\" aria-hidden=\"true\">\n {toneIcon[t.tone]}\n </span>\n )}\n <RadixToast.Title className=\"nuri-toast__message\">{t.message}</RadixToast.Title>\n </RadixToast.Root>\n ))}\n {/* 랜드마크 이름 — 스크린리더 영역 탐색용. {hotkey}는 Radix가 실제 키(F8)로 치환 */}\n <RadixToast.Viewport\n className={cx('nuri-toast__viewport', className)}\n label={`${label} ({hotkey})`}\n />\n </RadixToast.Provider>\n );\n}\n","'use client';\n\nimport { useSyncExternalStore } from 'react';\nimport type { ReactNode } from 'react';\n\nexport type ToastTone = 'default' | 'success' | 'error' | 'info';\n\nexport interface ToastOptions {\n /** 톤 — 아이콘·색 결정. @default 'default' (아이콘 없음) */\n tone?: ToastTone;\n /** 자동 닫힘까지 ms. @default 3000 */\n duration?: number;\n /** id 직접 지정 (같은 id면 교체 — 중복 방지·수동 dismiss용). */\n id?: string;\n}\n\nexport interface ToastRecord {\n id: string;\n message: ReactNode;\n tone: ToastTone;\n duration: number;\n}\n\n// ── React 밖 모듈 싱글톤 store (아무 데서나 toast() 호출 가능한 이유) ──\nlet toasts: ToastRecord[] = [];\nconst listeners = new Set<() => void>();\nconst EMPTY: ToastRecord[] = []; // 서버 스냅샷용 안정 참조\nlet counter = 0;\n\nconst emit = () => {\n for (const l of listeners) l();\n};\n\nfunction add(message: ReactNode, options: ToastOptions = {}): string {\n const id = options.id ?? `nuri-toast-${++counter}`;\n const record: ToastRecord = {\n id,\n message,\n tone: options.tone ?? 'default',\n duration: options.duration ?? 3000,\n };\n const idx = toasts.findIndex((t) => t.id === id);\n toasts = idx >= 0 ? toasts.map((t) => (t.id === id ? record : t)) : [...toasts, record];\n emit();\n return id;\n}\n\nfunction remove(id: string) {\n toasts = toasts.filter((t) => t.id !== id);\n emit();\n}\n\nfunction clear() {\n toasts = [];\n emit();\n}\n\nexport const toastStore = {\n add,\n remove,\n clear,\n subscribe(listener: () => void) {\n listeners.add(listener);\n return () => {\n listeners.delete(listener);\n };\n },\n getSnapshot: () => toasts,\n getServerSnapshot: () => EMPTY,\n};\n\n/** 현재 토스트 목록을 구독 (Toaster 내부 전용). */\nexport function useToasts(): ToastRecord[] {\n return useSyncExternalStore(\n toastStore.subscribe,\n toastStore.getSnapshot,\n toastStore.getServerSnapshot,\n );\n}\n","import type { ReactNode } from 'react';\nimport type { ToastTone } from './store.js';\n\n// 16px, currentColor — 색은 .nuri-toast--{tone}이 톤 토큰으로 지정.\nconst CheckIcon = () => (\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 16 16\" fill=\"none\" aria-hidden=\"true\">\n <path\n d=\"M3.5 8.5l3 3 6-6.5\"\n stroke=\"currentColor\"\n strokeWidth=\"1.6\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n />\n </svg>\n);\n\nconst ErrorIcon = () => (\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 16 16\" fill=\"none\" aria-hidden=\"true\">\n <path d=\"M8 4v4.5\" stroke=\"currentColor\" strokeWidth=\"1.6\" strokeLinecap=\"round\" />\n <circle cx=\"8\" cy=\"11.3\" r=\"0.95\" fill=\"currentColor\" />\n </svg>\n);\n\nconst InfoIcon = () => (\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 16 16\" fill=\"none\" aria-hidden=\"true\">\n <path d=\"M8 7.5v4\" stroke=\"currentColor\" strokeWidth=\"1.6\" strokeLinecap=\"round\" />\n <circle cx=\"8\" cy=\"4.7\" r=\"0.95\" fill=\"currentColor\" />\n </svg>\n);\n\n/** default 톤은 아이콘 없음. */\nexport const toneIcon: Record<Exclude<ToastTone, 'default'>, ReactNode> = {\n success: <CheckIcon />,\n error: <ErrorIcon />,\n info: <InfoIcon />,\n};\n","import type { ReactNode } from 'react';\nimport { toastStore } from './store.js';\nimport type { ToastOptions } from './store.js';\n\ntype ToneOptions = Omit<ToastOptions, 'tone'>;\n\n/**\n * 토스트를 띄운다. React 밖 모듈 함수라 컴포넌트·이벤트 핸들러·API 레이어 등\n * **어디서나** 호출 가능. 화면 렌더는 앱 루트에 1회 마운트한 `<Toaster/>`가 담당한다.\n *\n * @returns 토스트 id (수동 dismiss에 사용)\n * @example\n * toast.success('저장됐습니다');\n * const id = toast.error('오류', { duration: 5000 });\n * toast.dismiss(id);\n */\nfunction toastFn(message: ReactNode, options?: ToastOptions): string {\n return toastStore.add(message, options);\n}\n\nexport const toast = Object.assign(toastFn, {\n success: (message: ReactNode, options?: ToneOptions) =>\n toastStore.add(message, { ...options, tone: 'success' }),\n error: (message: ReactNode, options?: ToneOptions) =>\n toastStore.add(message, { ...options, tone: 'error' }),\n info: (message: ReactNode, options?: ToneOptions) =>\n toastStore.add(message, { ...options, tone: 'info' }),\n /** id 지정 시 해당 토스트만, 없으면 전체 제거. */\n dismiss: (id?: string) => (id ? toastStore.remove(id) : toastStore.clear()),\n});\n"],"mappings":"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","names":["jsx","jsxs","toasts"]}
|