@misael703/ui 1.36.1 → 1.36.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/{chunk-YXTHP7BD.js → chunk-D44PGK5V.js} +29 -8
- package/dist/chunk-D44PGK5V.js.map +1 -0
- package/dist/{chunk-KGU2VNAU.mjs → chunk-G5I2VVOC.mjs} +29 -8
- package/dist/chunk-G5I2VVOC.mjs.map +1 -0
- package/dist/components/Comments.js +4 -4
- package/dist/components/Comments.mjs +1 -1
- package/dist/index.js +4 -4
- package/dist/index.mjs +1 -1
- package/package.json +1 -1
- package/dist/chunk-KGU2VNAU.mjs.map +0 -1
- package/dist/chunk-YXTHP7BD.js.map +0 -1
|
@@ -47,7 +47,7 @@ function CommentThread({
|
|
|
47
47
|
const isInline = inputLayout === "inline";
|
|
48
48
|
const textareaRef = React__namespace.useRef(null);
|
|
49
49
|
const baselineRef = React__namespace.useRef(null);
|
|
50
|
-
React__namespace.
|
|
50
|
+
React__namespace.useEffect(() => {
|
|
51
51
|
if (!isInline) {
|
|
52
52
|
baselineRef.current = null;
|
|
53
53
|
setGrown(false);
|
|
@@ -55,11 +55,26 @@ function CommentThread({
|
|
|
55
55
|
}
|
|
56
56
|
const el = textareaRef.current;
|
|
57
57
|
if (!el) return;
|
|
58
|
-
|
|
59
|
-
const
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
58
|
+
let cancelled = false;
|
|
59
|
+
const measure = () => {
|
|
60
|
+
if (cancelled) return;
|
|
61
|
+
const node = textareaRef.current;
|
|
62
|
+
if (!node) return;
|
|
63
|
+
node.style.height = "auto";
|
|
64
|
+
const natural = node.offsetHeight;
|
|
65
|
+
const sh = node.scrollHeight;
|
|
66
|
+
if (baselineRef.current == null) baselineRef.current = sh;
|
|
67
|
+
node.style.height = `${Math.min(natural, INLINE_MAX_HEIGHT_PX)}px`;
|
|
68
|
+
setGrown(sh > (baselineRef.current ?? sh) + 4);
|
|
69
|
+
};
|
|
70
|
+
measure();
|
|
71
|
+
if (typeof document !== "undefined" && document.fonts && document.fonts.ready) {
|
|
72
|
+
document.fonts.ready.then(measure).catch(() => {
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
return () => {
|
|
76
|
+
cancelled = true;
|
|
77
|
+
};
|
|
63
78
|
}, [draft, isInline]);
|
|
64
79
|
const submit = () => {
|
|
65
80
|
if (!draft.trim() || !onAdd) return;
|
|
@@ -74,6 +89,12 @@ function CommentThread({
|
|
|
74
89
|
submit();
|
|
75
90
|
};
|
|
76
91
|
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: chunkPASF6T4H_js.cx("comments", className), ...rest, children: [
|
|
92
|
+
comments.length > 0 && // Skip the list element entirely when empty. `.comments` is a
|
|
93
|
+
// flex column with `gap: var(--space-4)`; an empty `<ul>` would
|
|
94
|
+
// still consume a gap slot, leaving a phantom 16px between
|
|
95
|
+
// "nothing" and the compose row. The visual symptom: the
|
|
96
|
+
// compose sits below the vertical center of the container in
|
|
97
|
+
// empty state.
|
|
77
98
|
/* @__PURE__ */ jsxRuntime.jsx("ul", { className: "comments__list", children: comments.map((c) => /* @__PURE__ */ jsxRuntime.jsxs("li", { className: chunkPASF6T4H_js.cx("comment", c.internal && "comment--internal"), children: [
|
|
78
99
|
/* @__PURE__ */ jsxRuntime.jsx(chunkAGMDYCGG_js.Avatar, { name: c.author.name, src: c.author.avatarSrc, size: 32 }),
|
|
79
100
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "comment__body", children: [
|
|
@@ -141,5 +162,5 @@ var _internal = { X: chunkC4AKMVDZ_js.X };
|
|
|
141
162
|
exports.AttachmentList = AttachmentList;
|
|
142
163
|
exports.CommentThread = CommentThread;
|
|
143
164
|
exports._internal = _internal;
|
|
144
|
-
//# sourceMappingURL=chunk-
|
|
145
|
-
//# sourceMappingURL=chunk-
|
|
165
|
+
//# sourceMappingURL=chunk-D44PGK5V.js.map
|
|
166
|
+
//# sourceMappingURL=chunk-D44PGK5V.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/components/Comments.tsx"],"names":["React","useLocale","jsxs","cx","jsx","Avatar","FileText","format","Download","Trash","X"],"mappings":"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","file":"chunk-D44PGK5V.js","sourcesContent":["'use client';\nimport * as React from 'react';\nimport { cx } from '../utils/cx';\nimport { Avatar } from './Display2';\nimport { FileText, Download, Trash, X } from './Icons';\nimport { useLocale } from '../locale/LocaleProvider';\nimport { format } from '../locale/messages';\n\n// ---------- CommentThread ----------------------------------------------\nexport interface CommentItem {\n id: string;\n author: { name: string; avatarSrc?: string };\n body: React.ReactNode;\n timestamp: React.ReactNode;\n internal?: boolean;\n}\n\nexport interface CommentThreadProps extends React.HTMLAttributes<HTMLDivElement> {\n comments: CommentItem[];\n onAdd?: (body: string, internal: boolean) => void;\n placeholder?: string;\n allowInternal?: boolean;\n /**\n * Compose layout. Default `'stacked'`: textarea on top, action row\n * below (form-style). `'inline'`: textarea and submit button share a\n * single row, the textarea auto-grows, and Enter submits while\n * Shift+Enter inserts a newline — chat convention (Slack/Linear).\n * `allowInternal` is ignored in `'inline'`; if you need the internal\n * toggle, use `'stacked'`.\n */\n inputLayout?: 'stacked' | 'inline';\n}\n\n// Inline mode auto-grow ceiling. ~5 lines at the kit's default\n// textarea font/line-height; beyond this the textarea scrolls.\nconst INLINE_MAX_HEIGHT_PX = 140;\n\nexport function CommentThread({\n comments, onAdd, placeholder,\n allowInternal = false, inputLayout = 'stacked',\n className, ...rest\n}: CommentThreadProps) {\n const [draft, setDraft] = React.useState('');\n const [internal, setInternal] = React.useState(false);\n const [grown, setGrown] = React.useState(false);\n const t = useLocale();\n const ph = placeholder ?? t['comments.placeholder'];\n const isInline = inputLayout === 'inline';\n const textareaRef = React.useRef<HTMLTextAreaElement>(null);\n // Captures the 1-line scrollHeight on first paint with the empty\n // textarea. Anything taller than baseline + epsilon means the user\n // has typed a newline (or wrapped past one line). Cleared when\n // leaving inline mode so a re-entry re-measures.\n const baselineRef = React.useRef<number | null>(null);\n\n // Auto-grow in inline mode: reset to single-row, then size to\n // scrollHeight, capped at INLINE_MAX_HEIGHT_PX. Runs on every draft\n // change so deletes shrink the box too. Same pass detects whether the\n // textarea has grown past its 1-line baseline so the wrap can flip\n // alignment from `center` (steady state) to `flex-end` (multi-line).\n // Stacked mode keeps the static rows={3} height — no DOM measurement,\n // no observable side-effect.\n React.useEffect(() => {\n if (!isInline) {\n baselineRef.current = null;\n setGrown(false);\n return;\n }\n const el = textareaRef.current;\n if (!el) return;\n // Auto-grow runs once now (with whatever font metrics are in play)\n // and once more after web fonts settle. Without the second pass,\n // a cold load that swaps from fallback to custom font (Outfit/DM\n // Sans here) leaves the textarea's calculated height 2px short\n // of the post-swap content box — `overflow-y: auto` then shows a\n // phantom scrollbar at rest until the next draft change. This was\n // the root cause of the 1.36.x scrollbar bug; both the\n // `scrollHeight + border` and the `offsetHeight` reads were\n // honest, the metric *itself* was changing under our feet as the\n // font finished loading. `useEffect` (not `useLayoutEffect`) so\n // the first measurement runs after paint, never during reconcile.\n let cancelled = false;\n const measure = () => {\n if (cancelled) return;\n const node = textareaRef.current;\n if (!node) return;\n node.style.height = 'auto';\n // `offsetHeight` after `'auto'` is the textarea's natural\n // rendered box, border-box border included — no separate border\n // math, no `getComputedStyle` round-trip.\n const natural = node.offsetHeight;\n // `scrollHeight` here is used only for the grown-vs-baseline\n // delta: what matters is the *change*, not the absolute.\n const sh = node.scrollHeight;\n if (baselineRef.current == null) baselineRef.current = sh;\n node.style.height = `${Math.min(natural, INLINE_MAX_HEIGHT_PX)}px`;\n // 4px epsilon absorbs subpixel rounding / font-metrics noise on\n // the baseline measurement; without it the wrap flicker-toggles\n // between center and flex-end at the boundary.\n setGrown(sh > (baselineRef.current ?? sh) + 4);\n };\n measure();\n // `document.fonts.ready` resolves immediately when no fonts are\n // pending (the hot path in prod where the font is cached), so the\n // re-measure is essentially free; on a cold load it fires when the\n // swap completes and the textarea reflows to its real metric.\n if (typeof document !== 'undefined' && document.fonts && document.fonts.ready) {\n document.fonts.ready.then(measure).catch(() => { /* fonts.ready can reject on aborted nav */ });\n }\n return () => { cancelled = true; };\n }, [draft, isInline]);\n\n const submit = () => {\n if (!draft.trim() || !onAdd) return;\n onAdd(draft, internal);\n setDraft('');\n setInternal(false);\n };\n\n // Enter submits in inline mode; Shift+Enter inserts a newline. In\n // stacked mode Enter keeps its default newline behaviour (form style).\n const onKeyDown = (e: React.KeyboardEvent<HTMLTextAreaElement>) => {\n if (!isInline) return;\n if (e.key !== 'Enter' || e.shiftKey) return;\n e.preventDefault();\n submit();\n };\n\n return (\n <div className={cx('comments', className)} {...rest}>\n {comments.length > 0 && (\n // Skip the list element entirely when empty. `.comments` is a\n // flex column with `gap: var(--space-4)`; an empty `<ul>` would\n // still consume a gap slot, leaving a phantom 16px between\n // \"nothing\" and the compose row. The visual symptom: the\n // compose sits below the vertical center of the container in\n // empty state.\n <ul className=\"comments__list\">\n {comments.map((c) => (\n <li key={c.id} className={cx('comment', c.internal && 'comment--internal')}>\n <Avatar name={c.author.name} src={c.author.avatarSrc} size={32} />\n <div className=\"comment__body\">\n <div className=\"comment__head\">\n <span className=\"comment__author\">{c.author.name}</span>\n <span className=\"comment__time\">{c.timestamp}</span>\n {c.internal && <span className=\"comment__tag\">{t['comments.internalTag']}</span>}\n </div>\n <div className=\"comment__text\">{c.body}</div>\n </div>\n </li>\n ))}\n </ul>\n )}\n {onAdd && (\n <div className={cx('comments__compose', isInline && 'comments__compose--inline', isInline && grown && 'is-grown')}>\n <textarea\n ref={textareaRef}\n className=\"textarea\"\n value={draft}\n onChange={(e) => setDraft(e.target.value)}\n onKeyDown={onKeyDown}\n placeholder={ph}\n rows={isInline ? 1 : 3}\n />\n {isInline ? (\n <button type=\"button\" className=\"btn btn--primary btn--sm comments__compose-submit\" disabled={!draft.trim()} onClick={submit}>\n {t['comments.send']}\n </button>\n ) : (\n <div className=\"comments__compose-actions\">\n {allowInternal && (\n <label className=\"comments__internal-toggle\">\n <input type=\"checkbox\" checked={internal} onChange={(e) => setInternal(e.target.checked)} />\n <span>{t['comments.internalOnly']}</span>\n </label>\n )}\n <button type=\"button\" className=\"btn btn--primary btn--sm\" disabled={!draft.trim()} onClick={submit}>\n {t['comments.send']}\n </button>\n </div>\n )}\n </div>\n )}\n </div>\n );\n}\n\n// ---------- AttachmentList ---------------------------------------------\nexport interface AttachmentItem {\n id: string;\n name: string;\n size?: string; // ya formateado: \"245 KB\"\n url?: string;\n uploadedBy?: string;\n uploadedAt?: React.ReactNode;\n icon?: React.ReactNode;\n onRemove?: () => void;\n}\n\nexport interface AttachmentListProps extends React.HTMLAttributes<HTMLUListElement> {\n attachments: AttachmentItem[];\n emptyMessage?: React.ReactNode;\n}\n\nexport function AttachmentList({ attachments, emptyMessage, className, ...rest }: AttachmentListProps) {\n const t = useLocale();\n const empty = emptyMessage ?? t['attachments.empty'];\n if (attachments.length === 0) {\n return <div className=\"attachments__empty\">{empty}</div>;\n }\n return (\n <ul className={cx('attachments', className)} {...rest}>\n {attachments.map((a) => (\n <li key={a.id} className=\"attachment\">\n <span className=\"attachment__icon\" aria-hidden=\"true\">\n {a.icon ?? <FileText size={20} />}\n </span>\n <div className=\"attachment__body\">\n <div className=\"attachment__name\">{a.name}</div>\n <div className=\"attachment__meta\">\n {a.size && <span>{a.size}</span>}\n {a.uploadedBy && <span>· {a.uploadedBy}</span>}\n {a.uploadedAt && <span>· {a.uploadedAt}</span>}\n </div>\n </div>\n <div className=\"attachment__actions\">\n {a.url && (\n <a href={a.url} download className=\"attachment__action\" aria-label={format(t['attachments.download'], { name: a.name })}>\n <Download size={16} />\n </a>\n )}\n {a.onRemove && (\n <button type=\"button\" className=\"attachment__action attachment__action--danger\" aria-label={format(t['attachments.remove'], { name: a.name })} onClick={a.onRemove}>\n <Trash size={16} />\n </button>\n )}\n </div>\n </li>\n ))}\n </ul>\n );\n}\n\n// re-export para que el bundle no tenga dead imports\nexport const _internal = { X };\n"]}
|
|
@@ -25,7 +25,7 @@ function CommentThread({
|
|
|
25
25
|
const isInline = inputLayout === "inline";
|
|
26
26
|
const textareaRef = React.useRef(null);
|
|
27
27
|
const baselineRef = React.useRef(null);
|
|
28
|
-
React.
|
|
28
|
+
React.useEffect(() => {
|
|
29
29
|
if (!isInline) {
|
|
30
30
|
baselineRef.current = null;
|
|
31
31
|
setGrown(false);
|
|
@@ -33,11 +33,26 @@ function CommentThread({
|
|
|
33
33
|
}
|
|
34
34
|
const el = textareaRef.current;
|
|
35
35
|
if (!el) return;
|
|
36
|
-
|
|
37
|
-
const
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
36
|
+
let cancelled = false;
|
|
37
|
+
const measure = () => {
|
|
38
|
+
if (cancelled) return;
|
|
39
|
+
const node = textareaRef.current;
|
|
40
|
+
if (!node) return;
|
|
41
|
+
node.style.height = "auto";
|
|
42
|
+
const natural = node.offsetHeight;
|
|
43
|
+
const sh = node.scrollHeight;
|
|
44
|
+
if (baselineRef.current == null) baselineRef.current = sh;
|
|
45
|
+
node.style.height = `${Math.min(natural, INLINE_MAX_HEIGHT_PX)}px`;
|
|
46
|
+
setGrown(sh > (baselineRef.current ?? sh) + 4);
|
|
47
|
+
};
|
|
48
|
+
measure();
|
|
49
|
+
if (typeof document !== "undefined" && document.fonts && document.fonts.ready) {
|
|
50
|
+
document.fonts.ready.then(measure).catch(() => {
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
return () => {
|
|
54
|
+
cancelled = true;
|
|
55
|
+
};
|
|
41
56
|
}, [draft, isInline]);
|
|
42
57
|
const submit = () => {
|
|
43
58
|
if (!draft.trim() || !onAdd) return;
|
|
@@ -52,6 +67,12 @@ function CommentThread({
|
|
|
52
67
|
submit();
|
|
53
68
|
};
|
|
54
69
|
return /* @__PURE__ */ jsxs("div", { className: cx("comments", className), ...rest, children: [
|
|
70
|
+
comments.length > 0 && // Skip the list element entirely when empty. `.comments` is a
|
|
71
|
+
// flex column with `gap: var(--space-4)`; an empty `<ul>` would
|
|
72
|
+
// still consume a gap slot, leaving a phantom 16px between
|
|
73
|
+
// "nothing" and the compose row. The visual symptom: the
|
|
74
|
+
// compose sits below the vertical center of the container in
|
|
75
|
+
// empty state.
|
|
55
76
|
/* @__PURE__ */ jsx("ul", { className: "comments__list", children: comments.map((c) => /* @__PURE__ */ jsxs("li", { className: cx("comment", c.internal && "comment--internal"), children: [
|
|
56
77
|
/* @__PURE__ */ jsx(Avatar, { name: c.author.name, src: c.author.avatarSrc, size: 32 }),
|
|
57
78
|
/* @__PURE__ */ jsxs("div", { className: "comment__body", children: [
|
|
@@ -117,5 +138,5 @@ function AttachmentList({ attachments, emptyMessage, className, ...rest }) {
|
|
|
117
138
|
var _internal = { X };
|
|
118
139
|
|
|
119
140
|
export { AttachmentList, CommentThread, _internal };
|
|
120
|
-
//# sourceMappingURL=chunk-
|
|
121
|
-
//# sourceMappingURL=chunk-
|
|
141
|
+
//# sourceMappingURL=chunk-G5I2VVOC.mjs.map
|
|
142
|
+
//# sourceMappingURL=chunk-G5I2VVOC.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/components/Comments.tsx"],"names":[],"mappings":"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","file":"chunk-G5I2VVOC.mjs","sourcesContent":["'use client';\nimport * as React from 'react';\nimport { cx } from '../utils/cx';\nimport { Avatar } from './Display2';\nimport { FileText, Download, Trash, X } from './Icons';\nimport { useLocale } from '../locale/LocaleProvider';\nimport { format } from '../locale/messages';\n\n// ---------- CommentThread ----------------------------------------------\nexport interface CommentItem {\n id: string;\n author: { name: string; avatarSrc?: string };\n body: React.ReactNode;\n timestamp: React.ReactNode;\n internal?: boolean;\n}\n\nexport interface CommentThreadProps extends React.HTMLAttributes<HTMLDivElement> {\n comments: CommentItem[];\n onAdd?: (body: string, internal: boolean) => void;\n placeholder?: string;\n allowInternal?: boolean;\n /**\n * Compose layout. Default `'stacked'`: textarea on top, action row\n * below (form-style). `'inline'`: textarea and submit button share a\n * single row, the textarea auto-grows, and Enter submits while\n * Shift+Enter inserts a newline — chat convention (Slack/Linear).\n * `allowInternal` is ignored in `'inline'`; if you need the internal\n * toggle, use `'stacked'`.\n */\n inputLayout?: 'stacked' | 'inline';\n}\n\n// Inline mode auto-grow ceiling. ~5 lines at the kit's default\n// textarea font/line-height; beyond this the textarea scrolls.\nconst INLINE_MAX_HEIGHT_PX = 140;\n\nexport function CommentThread({\n comments, onAdd, placeholder,\n allowInternal = false, inputLayout = 'stacked',\n className, ...rest\n}: CommentThreadProps) {\n const [draft, setDraft] = React.useState('');\n const [internal, setInternal] = React.useState(false);\n const [grown, setGrown] = React.useState(false);\n const t = useLocale();\n const ph = placeholder ?? t['comments.placeholder'];\n const isInline = inputLayout === 'inline';\n const textareaRef = React.useRef<HTMLTextAreaElement>(null);\n // Captures the 1-line scrollHeight on first paint with the empty\n // textarea. Anything taller than baseline + epsilon means the user\n // has typed a newline (or wrapped past one line). Cleared when\n // leaving inline mode so a re-entry re-measures.\n const baselineRef = React.useRef<number | null>(null);\n\n // Auto-grow in inline mode: reset to single-row, then size to\n // scrollHeight, capped at INLINE_MAX_HEIGHT_PX. Runs on every draft\n // change so deletes shrink the box too. Same pass detects whether the\n // textarea has grown past its 1-line baseline so the wrap can flip\n // alignment from `center` (steady state) to `flex-end` (multi-line).\n // Stacked mode keeps the static rows={3} height — no DOM measurement,\n // no observable side-effect.\n React.useEffect(() => {\n if (!isInline) {\n baselineRef.current = null;\n setGrown(false);\n return;\n }\n const el = textareaRef.current;\n if (!el) return;\n // Auto-grow runs once now (with whatever font metrics are in play)\n // and once more after web fonts settle. Without the second pass,\n // a cold load that swaps from fallback to custom font (Outfit/DM\n // Sans here) leaves the textarea's calculated height 2px short\n // of the post-swap content box — `overflow-y: auto` then shows a\n // phantom scrollbar at rest until the next draft change. This was\n // the root cause of the 1.36.x scrollbar bug; both the\n // `scrollHeight + border` and the `offsetHeight` reads were\n // honest, the metric *itself* was changing under our feet as the\n // font finished loading. `useEffect` (not `useLayoutEffect`) so\n // the first measurement runs after paint, never during reconcile.\n let cancelled = false;\n const measure = () => {\n if (cancelled) return;\n const node = textareaRef.current;\n if (!node) return;\n node.style.height = 'auto';\n // `offsetHeight` after `'auto'` is the textarea's natural\n // rendered box, border-box border included — no separate border\n // math, no `getComputedStyle` round-trip.\n const natural = node.offsetHeight;\n // `scrollHeight` here is used only for the grown-vs-baseline\n // delta: what matters is the *change*, not the absolute.\n const sh = node.scrollHeight;\n if (baselineRef.current == null) baselineRef.current = sh;\n node.style.height = `${Math.min(natural, INLINE_MAX_HEIGHT_PX)}px`;\n // 4px epsilon absorbs subpixel rounding / font-metrics noise on\n // the baseline measurement; without it the wrap flicker-toggles\n // between center and flex-end at the boundary.\n setGrown(sh > (baselineRef.current ?? sh) + 4);\n };\n measure();\n // `document.fonts.ready` resolves immediately when no fonts are\n // pending (the hot path in prod where the font is cached), so the\n // re-measure is essentially free; on a cold load it fires when the\n // swap completes and the textarea reflows to its real metric.\n if (typeof document !== 'undefined' && document.fonts && document.fonts.ready) {\n document.fonts.ready.then(measure).catch(() => { /* fonts.ready can reject on aborted nav */ });\n }\n return () => { cancelled = true; };\n }, [draft, isInline]);\n\n const submit = () => {\n if (!draft.trim() || !onAdd) return;\n onAdd(draft, internal);\n setDraft('');\n setInternal(false);\n };\n\n // Enter submits in inline mode; Shift+Enter inserts a newline. In\n // stacked mode Enter keeps its default newline behaviour (form style).\n const onKeyDown = (e: React.KeyboardEvent<HTMLTextAreaElement>) => {\n if (!isInline) return;\n if (e.key !== 'Enter' || e.shiftKey) return;\n e.preventDefault();\n submit();\n };\n\n return (\n <div className={cx('comments', className)} {...rest}>\n {comments.length > 0 && (\n // Skip the list element entirely when empty. `.comments` is a\n // flex column with `gap: var(--space-4)`; an empty `<ul>` would\n // still consume a gap slot, leaving a phantom 16px between\n // \"nothing\" and the compose row. The visual symptom: the\n // compose sits below the vertical center of the container in\n // empty state.\n <ul className=\"comments__list\">\n {comments.map((c) => (\n <li key={c.id} className={cx('comment', c.internal && 'comment--internal')}>\n <Avatar name={c.author.name} src={c.author.avatarSrc} size={32} />\n <div className=\"comment__body\">\n <div className=\"comment__head\">\n <span className=\"comment__author\">{c.author.name}</span>\n <span className=\"comment__time\">{c.timestamp}</span>\n {c.internal && <span className=\"comment__tag\">{t['comments.internalTag']}</span>}\n </div>\n <div className=\"comment__text\">{c.body}</div>\n </div>\n </li>\n ))}\n </ul>\n )}\n {onAdd && (\n <div className={cx('comments__compose', isInline && 'comments__compose--inline', isInline && grown && 'is-grown')}>\n <textarea\n ref={textareaRef}\n className=\"textarea\"\n value={draft}\n onChange={(e) => setDraft(e.target.value)}\n onKeyDown={onKeyDown}\n placeholder={ph}\n rows={isInline ? 1 : 3}\n />\n {isInline ? (\n <button type=\"button\" className=\"btn btn--primary btn--sm comments__compose-submit\" disabled={!draft.trim()} onClick={submit}>\n {t['comments.send']}\n </button>\n ) : (\n <div className=\"comments__compose-actions\">\n {allowInternal && (\n <label className=\"comments__internal-toggle\">\n <input type=\"checkbox\" checked={internal} onChange={(e) => setInternal(e.target.checked)} />\n <span>{t['comments.internalOnly']}</span>\n </label>\n )}\n <button type=\"button\" className=\"btn btn--primary btn--sm\" disabled={!draft.trim()} onClick={submit}>\n {t['comments.send']}\n </button>\n </div>\n )}\n </div>\n )}\n </div>\n );\n}\n\n// ---------- AttachmentList ---------------------------------------------\nexport interface AttachmentItem {\n id: string;\n name: string;\n size?: string; // ya formateado: \"245 KB\"\n url?: string;\n uploadedBy?: string;\n uploadedAt?: React.ReactNode;\n icon?: React.ReactNode;\n onRemove?: () => void;\n}\n\nexport interface AttachmentListProps extends React.HTMLAttributes<HTMLUListElement> {\n attachments: AttachmentItem[];\n emptyMessage?: React.ReactNode;\n}\n\nexport function AttachmentList({ attachments, emptyMessage, className, ...rest }: AttachmentListProps) {\n const t = useLocale();\n const empty = emptyMessage ?? t['attachments.empty'];\n if (attachments.length === 0) {\n return <div className=\"attachments__empty\">{empty}</div>;\n }\n return (\n <ul className={cx('attachments', className)} {...rest}>\n {attachments.map((a) => (\n <li key={a.id} className=\"attachment\">\n <span className=\"attachment__icon\" aria-hidden=\"true\">\n {a.icon ?? <FileText size={20} />}\n </span>\n <div className=\"attachment__body\">\n <div className=\"attachment__name\">{a.name}</div>\n <div className=\"attachment__meta\">\n {a.size && <span>{a.size}</span>}\n {a.uploadedBy && <span>· {a.uploadedBy}</span>}\n {a.uploadedAt && <span>· {a.uploadedAt}</span>}\n </div>\n </div>\n <div className=\"attachment__actions\">\n {a.url && (\n <a href={a.url} download className=\"attachment__action\" aria-label={format(t['attachments.download'], { name: a.name })}>\n <Download size={16} />\n </a>\n )}\n {a.onRemove && (\n <button type=\"button\" className=\"attachment__action attachment__action--danger\" aria-label={format(t['attachments.remove'], { name: a.name })} onClick={a.onRemove}>\n <Trash size={16} />\n </button>\n )}\n </div>\n </li>\n ))}\n </ul>\n );\n}\n\n// re-export para que el bundle no tenga dead imports\nexport const _internal = { X };\n"]}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
'use strict';
|
|
3
3
|
|
|
4
|
-
var
|
|
4
|
+
var chunkD44PGK5V_js = require('../chunk-D44PGK5V.js');
|
|
5
5
|
require('../chunk-AGMDYCGG.js');
|
|
6
6
|
require('../chunk-W725MLGN.js');
|
|
7
7
|
require('../chunk-WAGWB35Q.js');
|
|
@@ -15,15 +15,15 @@ require('../chunk-PASF6T4H.js');
|
|
|
15
15
|
|
|
16
16
|
Object.defineProperty(exports, "AttachmentList", {
|
|
17
17
|
enumerable: true,
|
|
18
|
-
get: function () { return
|
|
18
|
+
get: function () { return chunkD44PGK5V_js.AttachmentList; }
|
|
19
19
|
});
|
|
20
20
|
Object.defineProperty(exports, "CommentThread", {
|
|
21
21
|
enumerable: true,
|
|
22
|
-
get: function () { return
|
|
22
|
+
get: function () { return chunkD44PGK5V_js.CommentThread; }
|
|
23
23
|
});
|
|
24
24
|
Object.defineProperty(exports, "_internal", {
|
|
25
25
|
enumerable: true,
|
|
26
|
-
get: function () { return
|
|
26
|
+
get: function () { return chunkD44PGK5V_js._internal; }
|
|
27
27
|
});
|
|
28
28
|
//# sourceMappingURL=Comments.js.map
|
|
29
29
|
//# sourceMappingURL=Comments.js.map
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
'use client';
|
|
2
|
-
export { AttachmentList, CommentThread, _internal } from '../chunk-
|
|
2
|
+
export { AttachmentList, CommentThread, _internal } from '../chunk-G5I2VVOC.mjs';
|
|
3
3
|
import '../chunk-DH3IXJRJ.mjs';
|
|
4
4
|
import '../chunk-OWIPC5NO.mjs';
|
|
5
5
|
import '../chunk-FKBQYQQD.mjs';
|
package/dist/index.js
CHANGED
|
@@ -24,7 +24,7 @@ var chunkAJJBJQAH_js = require('./chunk-AJJBJQAH.js');
|
|
|
24
24
|
var chunk3JRP3DGM_js = require('./chunk-3JRP3DGM.js');
|
|
25
25
|
var chunk6II4NJQM_js = require('./chunk-6II4NJQM.js');
|
|
26
26
|
var chunkAWYCGJJX_js = require('./chunk-AWYCGJJX.js');
|
|
27
|
-
var
|
|
27
|
+
var chunkD44PGK5V_js = require('./chunk-D44PGK5V.js');
|
|
28
28
|
var chunkJJ54RD36_js = require('./chunk-JJ54RD36.js');
|
|
29
29
|
var chunkF3PTSBHH_js = require('./chunk-F3PTSBHH.js');
|
|
30
30
|
var chunk2PNXLTEM_js = require('./chunk-2PNXLTEM.js');
|
|
@@ -364,15 +364,15 @@ Object.defineProperty(exports, "CollapsibleTrigger", {
|
|
|
364
364
|
});
|
|
365
365
|
Object.defineProperty(exports, "AttachmentList", {
|
|
366
366
|
enumerable: true,
|
|
367
|
-
get: function () { return
|
|
367
|
+
get: function () { return chunkD44PGK5V_js.AttachmentList; }
|
|
368
368
|
});
|
|
369
369
|
Object.defineProperty(exports, "CommentThread", {
|
|
370
370
|
enumerable: true,
|
|
371
|
-
get: function () { return
|
|
371
|
+
get: function () { return chunkD44PGK5V_js.CommentThread; }
|
|
372
372
|
});
|
|
373
373
|
Object.defineProperty(exports, "_internal", {
|
|
374
374
|
enumerable: true,
|
|
375
|
-
get: function () { return
|
|
375
|
+
get: function () { return chunkD44PGK5V_js._internal; }
|
|
376
376
|
});
|
|
377
377
|
Object.defineProperty(exports, "AddressForm", {
|
|
378
378
|
enumerable: true,
|
package/dist/index.mjs
CHANGED
|
@@ -22,7 +22,7 @@ export { ImageGallery, Lightbox } from './chunk-DQO3VZ2X.mjs';
|
|
|
22
22
|
export { HoverCard } from './chunk-X36H75RR.mjs';
|
|
23
23
|
export { InputOTP } from './chunk-IVL7P3MF.mjs';
|
|
24
24
|
export { Collapsible, CollapsibleContent, CollapsibleTrigger } from './chunk-2UV37SEN.mjs';
|
|
25
|
-
export { AttachmentList, CommentThread, _internal } from './chunk-
|
|
25
|
+
export { AttachmentList, CommentThread, _internal } from './chunk-G5I2VVOC.mjs';
|
|
26
26
|
export { AddressForm, CartDrawer, CompareTable, FreeShippingProgress, OrderSummary, PriceDisplay, PromoCodeInput, QuantitySelector, Rating, VariantSelector, WishlistButton } from './chunk-FD3Y5IP7.mjs';
|
|
27
27
|
export { Drawer, Modal } from './chunk-VOSVV3LL.mjs';
|
|
28
28
|
export { ContextMenu } from './chunk-NS6CI6RP.mjs';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@misael703/ui",
|
|
3
|
-
"version": "1.36.
|
|
3
|
+
"version": "1.36.2",
|
|
4
4
|
"description": "Generic React + TypeScript UI kit, optimized for Next.js. Tokens, accessible components, runtime-configurable theming via presets.",
|
|
5
5
|
"author": "Misael Ocas <misael.ocas@gmail.com>",
|
|
6
6
|
"license": "MIT",
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/components/Comments.tsx"],"names":[],"mappings":"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","file":"chunk-KGU2VNAU.mjs","sourcesContent":["'use client';\nimport * as React from 'react';\nimport { cx } from '../utils/cx';\nimport { Avatar } from './Display2';\nimport { FileText, Download, Trash, X } from './Icons';\nimport { useLocale } from '../locale/LocaleProvider';\nimport { format } from '../locale/messages';\n\n// ---------- CommentThread ----------------------------------------------\nexport interface CommentItem {\n id: string;\n author: { name: string; avatarSrc?: string };\n body: React.ReactNode;\n timestamp: React.ReactNode;\n internal?: boolean;\n}\n\nexport interface CommentThreadProps extends React.HTMLAttributes<HTMLDivElement> {\n comments: CommentItem[];\n onAdd?: (body: string, internal: boolean) => void;\n placeholder?: string;\n allowInternal?: boolean;\n /**\n * Compose layout. Default `'stacked'`: textarea on top, action row\n * below (form-style). `'inline'`: textarea and submit button share a\n * single row, the textarea auto-grows, and Enter submits while\n * Shift+Enter inserts a newline — chat convention (Slack/Linear).\n * `allowInternal` is ignored in `'inline'`; if you need the internal\n * toggle, use `'stacked'`.\n */\n inputLayout?: 'stacked' | 'inline';\n}\n\n// Inline mode auto-grow ceiling. ~5 lines at the kit's default\n// textarea font/line-height; beyond this the textarea scrolls.\nconst INLINE_MAX_HEIGHT_PX = 140;\n\nexport function CommentThread({\n comments, onAdd, placeholder,\n allowInternal = false, inputLayout = 'stacked',\n className, ...rest\n}: CommentThreadProps) {\n const [draft, setDraft] = React.useState('');\n const [internal, setInternal] = React.useState(false);\n const [grown, setGrown] = React.useState(false);\n const t = useLocale();\n const ph = placeholder ?? t['comments.placeholder'];\n const isInline = inputLayout === 'inline';\n const textareaRef = React.useRef<HTMLTextAreaElement>(null);\n // Captures the 1-line scrollHeight on first paint with the empty\n // textarea. Anything taller than baseline + epsilon means the user\n // has typed a newline (or wrapped past one line). Cleared when\n // leaving inline mode so a re-entry re-measures.\n const baselineRef = React.useRef<number | null>(null);\n\n // Auto-grow in inline mode: reset to single-row, then size to\n // scrollHeight, capped at INLINE_MAX_HEIGHT_PX. Runs on every draft\n // change so deletes shrink the box too. Same pass detects whether the\n // textarea has grown past its 1-line baseline so the wrap can flip\n // alignment from `center` (steady state) to `flex-end` (multi-line).\n // Stacked mode keeps the static rows={3} height — no DOM measurement,\n // no observable side-effect.\n React.useLayoutEffect(() => {\n if (!isInline) {\n baselineRef.current = null;\n setGrown(false);\n return;\n }\n const el = textareaRef.current;\n if (!el) return;\n el.style.height = 'auto';\n const sh = el.scrollHeight;\n if (baselineRef.current == null) baselineRef.current = sh;\n el.style.height = `${Math.min(sh, INLINE_MAX_HEIGHT_PX)}px`;\n // 4px epsilon absorbs subpixel rounding / font-metrics noise on the\n // baseline measurement; without it the wrap flicker-toggles between\n // center and flex-end at the boundary.\n setGrown(sh > (baselineRef.current ?? sh) + 4);\n }, [draft, isInline]);\n\n const submit = () => {\n if (!draft.trim() || !onAdd) return;\n onAdd(draft, internal);\n setDraft('');\n setInternal(false);\n };\n\n // Enter submits in inline mode; Shift+Enter inserts a newline. In\n // stacked mode Enter keeps its default newline behaviour (form style).\n const onKeyDown = (e: React.KeyboardEvent<HTMLTextAreaElement>) => {\n if (!isInline) return;\n if (e.key !== 'Enter' || e.shiftKey) return;\n e.preventDefault();\n submit();\n };\n\n return (\n <div className={cx('comments', className)} {...rest}>\n <ul className=\"comments__list\">\n {comments.map((c) => (\n <li key={c.id} className={cx('comment', c.internal && 'comment--internal')}>\n <Avatar name={c.author.name} src={c.author.avatarSrc} size={32} />\n <div className=\"comment__body\">\n <div className=\"comment__head\">\n <span className=\"comment__author\">{c.author.name}</span>\n <span className=\"comment__time\">{c.timestamp}</span>\n {c.internal && <span className=\"comment__tag\">{t['comments.internalTag']}</span>}\n </div>\n <div className=\"comment__text\">{c.body}</div>\n </div>\n </li>\n ))}\n </ul>\n {onAdd && (\n <div className={cx('comments__compose', isInline && 'comments__compose--inline', isInline && grown && 'is-grown')}>\n <textarea\n ref={textareaRef}\n className=\"textarea\"\n value={draft}\n onChange={(e) => setDraft(e.target.value)}\n onKeyDown={onKeyDown}\n placeholder={ph}\n rows={isInline ? 1 : 3}\n />\n {isInline ? (\n <button type=\"button\" className=\"btn btn--primary btn--sm comments__compose-submit\" disabled={!draft.trim()} onClick={submit}>\n {t['comments.send']}\n </button>\n ) : (\n <div className=\"comments__compose-actions\">\n {allowInternal && (\n <label className=\"comments__internal-toggle\">\n <input type=\"checkbox\" checked={internal} onChange={(e) => setInternal(e.target.checked)} />\n <span>{t['comments.internalOnly']}</span>\n </label>\n )}\n <button type=\"button\" className=\"btn btn--primary btn--sm\" disabled={!draft.trim()} onClick={submit}>\n {t['comments.send']}\n </button>\n </div>\n )}\n </div>\n )}\n </div>\n );\n}\n\n// ---------- AttachmentList ---------------------------------------------\nexport interface AttachmentItem {\n id: string;\n name: string;\n size?: string; // ya formateado: \"245 KB\"\n url?: string;\n uploadedBy?: string;\n uploadedAt?: React.ReactNode;\n icon?: React.ReactNode;\n onRemove?: () => void;\n}\n\nexport interface AttachmentListProps extends React.HTMLAttributes<HTMLUListElement> {\n attachments: AttachmentItem[];\n emptyMessage?: React.ReactNode;\n}\n\nexport function AttachmentList({ attachments, emptyMessage, className, ...rest }: AttachmentListProps) {\n const t = useLocale();\n const empty = emptyMessage ?? t['attachments.empty'];\n if (attachments.length === 0) {\n return <div className=\"attachments__empty\">{empty}</div>;\n }\n return (\n <ul className={cx('attachments', className)} {...rest}>\n {attachments.map((a) => (\n <li key={a.id} className=\"attachment\">\n <span className=\"attachment__icon\" aria-hidden=\"true\">\n {a.icon ?? <FileText size={20} />}\n </span>\n <div className=\"attachment__body\">\n <div className=\"attachment__name\">{a.name}</div>\n <div className=\"attachment__meta\">\n {a.size && <span>{a.size}</span>}\n {a.uploadedBy && <span>· {a.uploadedBy}</span>}\n {a.uploadedAt && <span>· {a.uploadedAt}</span>}\n </div>\n </div>\n <div className=\"attachment__actions\">\n {a.url && (\n <a href={a.url} download className=\"attachment__action\" aria-label={format(t['attachments.download'], { name: a.name })}>\n <Download size={16} />\n </a>\n )}\n {a.onRemove && (\n <button type=\"button\" className=\"attachment__action attachment__action--danger\" aria-label={format(t['attachments.remove'], { name: a.name })} onClick={a.onRemove}>\n <Trash size={16} />\n </button>\n )}\n </div>\n </li>\n ))}\n </ul>\n );\n}\n\n// re-export para que el bundle no tenga dead imports\nexport const _internal = { X };\n"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/components/Comments.tsx"],"names":["React","useLocale","jsxs","cx","jsx","Avatar","FileText","format","Download","Trash","X"],"mappings":"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","file":"chunk-YXTHP7BD.js","sourcesContent":["'use client';\nimport * as React from 'react';\nimport { cx } from '../utils/cx';\nimport { Avatar } from './Display2';\nimport { FileText, Download, Trash, X } from './Icons';\nimport { useLocale } from '../locale/LocaleProvider';\nimport { format } from '../locale/messages';\n\n// ---------- CommentThread ----------------------------------------------\nexport interface CommentItem {\n id: string;\n author: { name: string; avatarSrc?: string };\n body: React.ReactNode;\n timestamp: React.ReactNode;\n internal?: boolean;\n}\n\nexport interface CommentThreadProps extends React.HTMLAttributes<HTMLDivElement> {\n comments: CommentItem[];\n onAdd?: (body: string, internal: boolean) => void;\n placeholder?: string;\n allowInternal?: boolean;\n /**\n * Compose layout. Default `'stacked'`: textarea on top, action row\n * below (form-style). `'inline'`: textarea and submit button share a\n * single row, the textarea auto-grows, and Enter submits while\n * Shift+Enter inserts a newline — chat convention (Slack/Linear).\n * `allowInternal` is ignored in `'inline'`; if you need the internal\n * toggle, use `'stacked'`.\n */\n inputLayout?: 'stacked' | 'inline';\n}\n\n// Inline mode auto-grow ceiling. ~5 lines at the kit's default\n// textarea font/line-height; beyond this the textarea scrolls.\nconst INLINE_MAX_HEIGHT_PX = 140;\n\nexport function CommentThread({\n comments, onAdd, placeholder,\n allowInternal = false, inputLayout = 'stacked',\n className, ...rest\n}: CommentThreadProps) {\n const [draft, setDraft] = React.useState('');\n const [internal, setInternal] = React.useState(false);\n const [grown, setGrown] = React.useState(false);\n const t = useLocale();\n const ph = placeholder ?? t['comments.placeholder'];\n const isInline = inputLayout === 'inline';\n const textareaRef = React.useRef<HTMLTextAreaElement>(null);\n // Captures the 1-line scrollHeight on first paint with the empty\n // textarea. Anything taller than baseline + epsilon means the user\n // has typed a newline (or wrapped past one line). Cleared when\n // leaving inline mode so a re-entry re-measures.\n const baselineRef = React.useRef<number | null>(null);\n\n // Auto-grow in inline mode: reset to single-row, then size to\n // scrollHeight, capped at INLINE_MAX_HEIGHT_PX. Runs on every draft\n // change so deletes shrink the box too. Same pass detects whether the\n // textarea has grown past its 1-line baseline so the wrap can flip\n // alignment from `center` (steady state) to `flex-end` (multi-line).\n // Stacked mode keeps the static rows={3} height — no DOM measurement,\n // no observable side-effect.\n React.useLayoutEffect(() => {\n if (!isInline) {\n baselineRef.current = null;\n setGrown(false);\n return;\n }\n const el = textareaRef.current;\n if (!el) return;\n el.style.height = 'auto';\n const sh = el.scrollHeight;\n if (baselineRef.current == null) baselineRef.current = sh;\n el.style.height = `${Math.min(sh, INLINE_MAX_HEIGHT_PX)}px`;\n // 4px epsilon absorbs subpixel rounding / font-metrics noise on the\n // baseline measurement; without it the wrap flicker-toggles between\n // center and flex-end at the boundary.\n setGrown(sh > (baselineRef.current ?? sh) + 4);\n }, [draft, isInline]);\n\n const submit = () => {\n if (!draft.trim() || !onAdd) return;\n onAdd(draft, internal);\n setDraft('');\n setInternal(false);\n };\n\n // Enter submits in inline mode; Shift+Enter inserts a newline. In\n // stacked mode Enter keeps its default newline behaviour (form style).\n const onKeyDown = (e: React.KeyboardEvent<HTMLTextAreaElement>) => {\n if (!isInline) return;\n if (e.key !== 'Enter' || e.shiftKey) return;\n e.preventDefault();\n submit();\n };\n\n return (\n <div className={cx('comments', className)} {...rest}>\n <ul className=\"comments__list\">\n {comments.map((c) => (\n <li key={c.id} className={cx('comment', c.internal && 'comment--internal')}>\n <Avatar name={c.author.name} src={c.author.avatarSrc} size={32} />\n <div className=\"comment__body\">\n <div className=\"comment__head\">\n <span className=\"comment__author\">{c.author.name}</span>\n <span className=\"comment__time\">{c.timestamp}</span>\n {c.internal && <span className=\"comment__tag\">{t['comments.internalTag']}</span>}\n </div>\n <div className=\"comment__text\">{c.body}</div>\n </div>\n </li>\n ))}\n </ul>\n {onAdd && (\n <div className={cx('comments__compose', isInline && 'comments__compose--inline', isInline && grown && 'is-grown')}>\n <textarea\n ref={textareaRef}\n className=\"textarea\"\n value={draft}\n onChange={(e) => setDraft(e.target.value)}\n onKeyDown={onKeyDown}\n placeholder={ph}\n rows={isInline ? 1 : 3}\n />\n {isInline ? (\n <button type=\"button\" className=\"btn btn--primary btn--sm comments__compose-submit\" disabled={!draft.trim()} onClick={submit}>\n {t['comments.send']}\n </button>\n ) : (\n <div className=\"comments__compose-actions\">\n {allowInternal && (\n <label className=\"comments__internal-toggle\">\n <input type=\"checkbox\" checked={internal} onChange={(e) => setInternal(e.target.checked)} />\n <span>{t['comments.internalOnly']}</span>\n </label>\n )}\n <button type=\"button\" className=\"btn btn--primary btn--sm\" disabled={!draft.trim()} onClick={submit}>\n {t['comments.send']}\n </button>\n </div>\n )}\n </div>\n )}\n </div>\n );\n}\n\n// ---------- AttachmentList ---------------------------------------------\nexport interface AttachmentItem {\n id: string;\n name: string;\n size?: string; // ya formateado: \"245 KB\"\n url?: string;\n uploadedBy?: string;\n uploadedAt?: React.ReactNode;\n icon?: React.ReactNode;\n onRemove?: () => void;\n}\n\nexport interface AttachmentListProps extends React.HTMLAttributes<HTMLUListElement> {\n attachments: AttachmentItem[];\n emptyMessage?: React.ReactNode;\n}\n\nexport function AttachmentList({ attachments, emptyMessage, className, ...rest }: AttachmentListProps) {\n const t = useLocale();\n const empty = emptyMessage ?? t['attachments.empty'];\n if (attachments.length === 0) {\n return <div className=\"attachments__empty\">{empty}</div>;\n }\n return (\n <ul className={cx('attachments', className)} {...rest}>\n {attachments.map((a) => (\n <li key={a.id} className=\"attachment\">\n <span className=\"attachment__icon\" aria-hidden=\"true\">\n {a.icon ?? <FileText size={20} />}\n </span>\n <div className=\"attachment__body\">\n <div className=\"attachment__name\">{a.name}</div>\n <div className=\"attachment__meta\">\n {a.size && <span>{a.size}</span>}\n {a.uploadedBy && <span>· {a.uploadedBy}</span>}\n {a.uploadedAt && <span>· {a.uploadedAt}</span>}\n </div>\n </div>\n <div className=\"attachment__actions\">\n {a.url && (\n <a href={a.url} download className=\"attachment__action\" aria-label={format(t['attachments.download'], { name: a.name })}>\n <Download size={16} />\n </a>\n )}\n {a.onRemove && (\n <button type=\"button\" className=\"attachment__action attachment__action--danger\" aria-label={format(t['attachments.remove'], { name: a.name })} onClick={a.onRemove}>\n <Trash size={16} />\n </button>\n )}\n </div>\n </li>\n ))}\n </ul>\n );\n}\n\n// re-export para que el bundle no tenga dead imports\nexport const _internal = { X };\n"]}
|