@umodeapporg/ui 0.8.0 → 0.8.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/components/DateTime/DatePicker.d.ts +1 -1
- package/dist/components/DateTime/DatePicker.js +2 -2
- package/dist/components/DateTime/DatePicker.js.map +1 -1
- package/dist/components/Form/attachments/AttachmentPreviewList.js +67 -61
- package/dist/components/Form/attachments/AttachmentPreviewList.js.map +1 -1
- package/package.json +1 -1
|
@@ -12,7 +12,7 @@ export interface IDatePickerProps {
|
|
|
12
12
|
startValue?: Date;
|
|
13
13
|
onFocus?: () => void;
|
|
14
14
|
onBlur?: () => void;
|
|
15
|
-
/** Extra classes for the inner input
|
|
15
|
+
/** Extra classes for the inner input, which is a `ControlledInput` like any other field. */
|
|
16
16
|
inputClassName?: string;
|
|
17
17
|
}
|
|
18
18
|
declare const DatePicker: React.FC<IDatePickerProps>;
|
|
@@ -79,13 +79,13 @@ const DatePicker = ({
|
|
|
79
79
|
{
|
|
80
80
|
readOnly: true,
|
|
81
81
|
placeholder: placeholder ?? label,
|
|
82
|
-
className: twMerge("
|
|
82
|
+
className: twMerge("focus:border-border-strong", inputClassName),
|
|
83
83
|
disabled,
|
|
84
84
|
value: date ? formatDate(date) : "",
|
|
85
85
|
onClick: togglePopover
|
|
86
86
|
}
|
|
87
87
|
),
|
|
88
|
-
date && /* @__PURE__ */ jsx(ControlledTooltip, { content: t("dates.clearSelection"), children: /* @__PURE__ */ jsx("div", { onClick: clear, className: "absolute inset-y-0 right-4
|
|
88
|
+
date && /* @__PURE__ */ jsx(ControlledTooltip, { content: t("dates.clearSelection"), children: /* @__PURE__ */ jsx("div", { onClick: clear, className: "absolute inset-y-0 right-4 flex cursor-pointer items-center", children: /* @__PURE__ */ jsx(MaterialSymbol, { icon: "close_small", size: 20, color: "rgb(var(--color-foreground-subtle))" }) }) })
|
|
89
89
|
] })
|
|
90
90
|
] }) }),
|
|
91
91
|
/* @__PURE__ */ jsx(ControlledPopoverContent, { className: "zIndexMax m-0 p-0", children: /* @__PURE__ */ jsx(
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DatePicker.js","sources":["../../../src/components/DateTime/DatePicker.tsx"],"sourcesContent":["import { useEffect, useRef, useState } from 'react';\nimport * as React from 'react';\nimport { ptBR } from 'date-fns/locale';\nimport { DateAfter, DateBefore, DayPicker } from 'react-day-picker';\nimport { useTranslation } from 'react-i18next';\nimport { twMerge } from 'tailwind-merge';\n\nimport {\n ControlledPopoverContent,\n ControlledPopoverHandler,\n ControlledPopoverRoot,\n} from '@/components/UI/ControlledPopover';\nimport ControlledTooltip from '@/components/UI/ControlledTooltip';\nimport { formatDate } from '@/helpers/dateHelpers';\n\nimport ControlledInput from '../Form/ControlledInput';\nimport { MaterialSymbol } from '../UI/MaterialSymbol';\n\nimport 'react-day-picker/style.css';\n\nconst DATE_PICKER_TIMEZONE = 'UTC';\n\nexport interface IDatePickerProps {\n label?: string;\n /** Text shown inside the empty input. Falls back to the label for the historical behavior. */\n placeholder?: string;\n onUpdate: (value: string | undefined) => void;\n disabled?: boolean;\n minDate?: string;\n maxDate?: string;\n useIcon?: boolean;\n valueRender?: () => React.ReactNode;\n startValue?: Date;\n onFocus?: () => void;\n onBlur?: () => void;\n /** Extra classes for the inner input
|
|
1
|
+
{"version":3,"file":"DatePicker.js","sources":["../../../src/components/DateTime/DatePicker.tsx"],"sourcesContent":["import { useEffect, useRef, useState } from 'react';\nimport * as React from 'react';\nimport { ptBR } from 'date-fns/locale';\nimport { DateAfter, DateBefore, DayPicker } from 'react-day-picker';\nimport { useTranslation } from 'react-i18next';\nimport { twMerge } from 'tailwind-merge';\n\nimport {\n ControlledPopoverContent,\n ControlledPopoverHandler,\n ControlledPopoverRoot,\n} from '@/components/UI/ControlledPopover';\nimport ControlledTooltip from '@/components/UI/ControlledTooltip';\nimport { formatDate } from '@/helpers/dateHelpers';\n\nimport ControlledInput from '../Form/ControlledInput';\nimport { MaterialSymbol } from '../UI/MaterialSymbol';\n\nimport 'react-day-picker/style.css';\n\nconst DATE_PICKER_TIMEZONE = 'UTC';\n\nexport interface IDatePickerProps {\n label?: string;\n /** Text shown inside the empty input. Falls back to the label for the historical behavior. */\n placeholder?: string;\n onUpdate: (value: string | undefined) => void;\n disabled?: boolean;\n minDate?: string;\n maxDate?: string;\n useIcon?: boolean;\n valueRender?: () => React.ReactNode;\n startValue?: Date;\n onFocus?: () => void;\n onBlur?: () => void;\n /** Extra classes for the inner input, which is a `ControlledInput` like any other field. */\n inputClassName?: string;\n}\n\nfunction formatDateToYYYYMMDD(date: Date) {\n const month = String(date.getUTCMonth() + 1).padStart(2, '0');\n const day = String(date.getUTCDate()).padStart(2, '0');\n const year = date.getUTCFullYear();\n return `${year}-${month}-${day}`;\n}\n\nfunction parseDate(dateString: string): Date {\n const dateParts = dateString.split('T')[0].split('-');\n const year = parseInt(dateParts[0], 10);\n const month = parseInt(dateParts[1], 10) - 1;\n const day = parseInt(dateParts[2], 10);\n\n const formattedDate = new Date(year, month, day);\n return formattedDate;\n}\n\nconst DatePicker: React.FC<IDatePickerProps> = ({\n label,\n placeholder,\n disabled,\n onUpdate,\n minDate,\n maxDate,\n useIcon,\n valueRender,\n startValue,\n onFocus,\n inputClassName,\n}) => {\n const { t } = useTranslation('ui');\n const [date, setDate] = useState<Date | undefined>(startValue || undefined);\n const [popoverOpen, setPopoverOpen] = useState(false);\n\n // Hosts pass a freshly parsed Date on every render, so the sync compares\n // timestamps. Without this the picker ignored external changes (clearing\n // filters, applying a preset) and callers had to force a remount via `key`.\n const startTime = startValue ? startValue.getTime() : null;\n const syncedStartTimeRef = useRef(startTime);\n\n useEffect(() => {\n if (syncedStartTimeRef.current === startTime) return;\n syncedStartTimeRef.current = startTime;\n setDate(startTime === null ? undefined : new Date(startTime));\n }, [startTime]);\n\n const handleSelectedDate = (date: Date | undefined) => {\n setDate(date);\n if (date) {\n const formattedDate = formatDateToYYYYMMDD(date);\n onUpdate(formattedDate);\n } else {\n onUpdate(undefined);\n }\n setPopoverOpen(false);\n };\n\n const togglePopover = () => {\n setPopoverOpen(!popoverOpen);\n };\n\n const clear = () => {\n handleSelectedDate(undefined);\n };\n\n const beforeMatcher: DateBefore = {\n before: minDate ? parseDate(String(minDate)) : new Date(2024, 1, 1),\n };\n const afterMatcher: DateAfter = {\n after: maxDate ? parseDate(String(maxDate)) : new Date(2030, 1, 1),\n };\n\n return (\n <ControlledPopoverRoot placement=\"bottom\" open={popoverOpen} handler={setPopoverOpen}>\n <ControlledPopoverHandler>\n <div className=\"zIndexMax flex w-full flex-row items-center gap-x-1\" onFocus={onFocus}>\n {useIcon && <MaterialSymbol icon=\"calendar_month\" size={20} />}\n {valueRender ? (\n valueRender()\n ) : (\n <div className=\"relative w-full\">\n <ControlledInput\n readOnly\n placeholder={placeholder ?? label}\n className={twMerge('focus:border-border-strong', inputClassName)}\n disabled={disabled}\n value={date ? formatDate(date) : ''}\n onClick={togglePopover}\n />\n\n {date && (\n <ControlledTooltip content={t('dates.clearSelection')}>\n <div onClick={clear} className=\"absolute inset-y-0 right-4 flex cursor-pointer items-center\">\n <MaterialSymbol icon=\"close_small\" size={20} color=\"rgb(var(--color-foreground-subtle))\" />\n </div>\n </ControlledTooltip>\n )}\n </div>\n )}\n </div>\n </ControlledPopoverHandler>\n <ControlledPopoverContent className=\"zIndexMax m-0 p-0\">\n <DayPicker\n ISOWeek\n showOutsideDays\n captionLayout=\"label\"\n dir=\"ltr\"\n firstWeekContainsDate={1}\n mode=\"single\"\n numberOfMonths={1}\n timeZone={DATE_PICKER_TIMEZONE}\n weekStartsOn={1}\n locale={ptBR}\n selected={date}\n defaultMonth={date}\n disabled={[beforeMatcher, afterMatcher]}\n onSelect={handleSelectedDate}\n className=\"p-3\"\n classNames={{\n today: `rounded-md bg-muted-active text-foreground`,\n selected: `rounded-md bg-ink text-white hover:bg-ink hover:text-white focus:bg-ink focus:text-white`,\n chevron: `fill-foreground`,\n }}\n />\n </ControlledPopoverContent>\n </ControlledPopoverRoot>\n );\n};\n\nexport default DatePicker;\n"],"names":["date"],"mappings":"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"}
|
|
@@ -15,70 +15,76 @@ const AttachmentPreviewList = ({
|
|
|
15
15
|
}) => {
|
|
16
16
|
const { t } = useTranslation("ui");
|
|
17
17
|
if (!stagedAttachments.length) return null;
|
|
18
|
-
return /* @__PURE__ */ jsx("div", { className: "flex flex-wrap gap-2", children: stagedAttachments.map((attachment) =>
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
18
|
+
return /* @__PURE__ */ jsx("div", { className: "flex flex-wrap gap-2", children: stagedAttachments.map((attachment) => {
|
|
19
|
+
const actions = isEditableImage(attachment) ? (imageActions ?? []).filter((action) => !action.hidden?.(attachment)) : [];
|
|
20
|
+
return /* @__PURE__ */ jsxs(
|
|
21
|
+
"div",
|
|
22
|
+
{
|
|
23
|
+
className: classNames(
|
|
24
|
+
"relative flex animate-pop-in flex-col overflow-hidden rounded-xl border bg-surface",
|
|
25
|
+
// The buttons sit on the same row as the remove one, so the chip grows to keep the
|
|
26
|
+
// file name readable instead of squeezing it to a couple of characters.
|
|
27
|
+
actions.length > 0 ? "w-56" : "w-44",
|
|
28
|
+
attachment.status === "error" ? "border-danger-border" : "border-border"
|
|
29
|
+
),
|
|
30
|
+
children: [
|
|
31
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-x-2 p-2", children: [
|
|
32
|
+
attachment.previewUrl && attachment.file.type.startsWith("image/") ? /* @__PURE__ */ jsx(ControlledImage, { src: attachment.previewUrl, className: "size-10 shrink-0 rounded-lg object-cover" }) : /* @__PURE__ */ jsx("span", { className: "flex size-10 shrink-0 items-center justify-center rounded-lg bg-brand-soft text-brand-emphasis", children: /* @__PURE__ */ jsx(MaterialSymbol, { icon: iconForMimeType(attachment.file.type), size: 20 }) }),
|
|
33
|
+
/* @__PURE__ */ jsxs("span", { className: "flex min-w-0 flex-1 flex-col", children: [
|
|
34
|
+
/* @__PURE__ */ jsx("span", { className: "truncate text-xs font-medium text-foreground", children: attachment.file.name }),
|
|
35
|
+
/* @__PURE__ */ jsx("span", { className: "text-xxs text-foreground-subtle", children: attachment.status === "error" ? t("followUp.attachments.uploadFailed") : formatFileSize(attachment.file.size) })
|
|
36
|
+
] }),
|
|
37
|
+
actions.map((action) => /* @__PURE__ */ jsx(ControlledTooltip, { content: action.label, className: "z-[9999]", children: /* @__PURE__ */ jsx(
|
|
38
|
+
ControlledButton,
|
|
39
|
+
{
|
|
40
|
+
defaultHtmlButton: true,
|
|
41
|
+
onClick: () => action.onClick(attachment),
|
|
42
|
+
className: "flex size-6 shrink-0 items-center justify-center rounded-full text-foreground-faint transition-colors hover:bg-muted-hover hover:text-brand-emphasis",
|
|
43
|
+
children: /* @__PURE__ */ jsx(MaterialSymbol, { icon: action.icon, size: 14 })
|
|
44
|
+
}
|
|
45
|
+
) }, action.key)),
|
|
46
|
+
attachment.status === "error" && /* @__PURE__ */ jsx(ControlledTooltip, { content: t("actions.tryAgain"), className: "z-[9999]", children: /* @__PURE__ */ jsx(
|
|
47
|
+
ControlledButton,
|
|
48
|
+
{
|
|
49
|
+
defaultHtmlButton: true,
|
|
50
|
+
onClick: () => onRetry(attachment.stagingId),
|
|
51
|
+
className: "flex size-6 shrink-0 items-center justify-center rounded-full text-danger-emphasis hover:bg-danger-soft",
|
|
52
|
+
children: /* @__PURE__ */ jsx(MaterialSymbol, { icon: "refresh", size: 14 })
|
|
53
|
+
}
|
|
54
|
+
) }),
|
|
55
|
+
/* @__PURE__ */ jsx(ControlledTooltip, { content: t("actions.remove"), className: "z-[9999]", children: /* @__PURE__ */ jsx(
|
|
56
|
+
ControlledButton,
|
|
57
|
+
{
|
|
58
|
+
defaultHtmlButton: true,
|
|
59
|
+
onClick: () => onRemove(attachment.stagingId),
|
|
60
|
+
className: "flex size-6 shrink-0 items-center justify-center rounded-full text-foreground-faint hover:bg-muted-hover hover:text-foreground-muted",
|
|
61
|
+
children: /* @__PURE__ */ jsx(MaterialSymbol, { icon: "close", size: 14 })
|
|
62
|
+
}
|
|
63
|
+
) })
|
|
31
64
|
] }),
|
|
32
|
-
attachment.status === "
|
|
33
|
-
|
|
34
|
-
{
|
|
35
|
-
defaultHtmlButton: true,
|
|
36
|
-
onClick: () => onRetry(attachment.stagingId),
|
|
37
|
-
className: "flex size-6 shrink-0 items-center justify-center rounded-full text-danger-emphasis hover:bg-danger-soft",
|
|
38
|
-
children: /* @__PURE__ */ jsx(MaterialSymbol, { icon: "refresh", size: 14 })
|
|
39
|
-
}
|
|
40
|
-
) }),
|
|
41
|
-
/* @__PURE__ */ jsx(ControlledTooltip, { content: t("actions.remove"), className: "z-[9999]", children: /* @__PURE__ */ jsx(
|
|
42
|
-
ControlledButton,
|
|
65
|
+
attachment.status === "uploading" && /* @__PURE__ */ jsx(
|
|
66
|
+
"div",
|
|
43
67
|
{
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
68
|
+
className: "h-1 w-full bg-muted-hover",
|
|
69
|
+
role: "progressbar",
|
|
70
|
+
"aria-valuemin": 0,
|
|
71
|
+
"aria-valuemax": 100,
|
|
72
|
+
"aria-valuenow": attachment.progress,
|
|
73
|
+
"aria-label": t("followUp.attachments.uploading"),
|
|
74
|
+
children: /* @__PURE__ */ jsx(
|
|
75
|
+
"div",
|
|
76
|
+
{
|
|
77
|
+
className: "h-1 rounded-r-full bg-brand transition-interactive duration-base",
|
|
78
|
+
style: { width: `${attachment.progress}%` }
|
|
79
|
+
}
|
|
80
|
+
)
|
|
48
81
|
}
|
|
49
|
-
)
|
|
50
|
-
]
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
onClick: () => action.onClick(attachment),
|
|
56
|
-
className: "flex size-6 shrink-0 items-center justify-center rounded-full text-foreground-faint transition-colors hover:bg-muted-hover hover:text-brand-emphasis",
|
|
57
|
-
children: /* @__PURE__ */ jsx(MaterialSymbol, { icon: action.icon, size: 14 })
|
|
58
|
-
}
|
|
59
|
-
) }, action.key)) }),
|
|
60
|
-
attachment.status === "uploading" && /* @__PURE__ */ jsx(
|
|
61
|
-
"div",
|
|
62
|
-
{
|
|
63
|
-
className: "h-1 w-full bg-muted-hover",
|
|
64
|
-
role: "progressbar",
|
|
65
|
-
"aria-valuemin": 0,
|
|
66
|
-
"aria-valuemax": 100,
|
|
67
|
-
"aria-valuenow": attachment.progress,
|
|
68
|
-
"aria-label": t("followUp.attachments.uploading"),
|
|
69
|
-
children: /* @__PURE__ */ jsx(
|
|
70
|
-
"div",
|
|
71
|
-
{
|
|
72
|
-
className: "h-1 rounded-r-full bg-brand transition-interactive duration-base",
|
|
73
|
-
style: { width: `${attachment.progress}%` }
|
|
74
|
-
}
|
|
75
|
-
)
|
|
76
|
-
}
|
|
77
|
-
)
|
|
78
|
-
]
|
|
79
|
-
},
|
|
80
|
-
attachment.stagingId
|
|
81
|
-
)) });
|
|
82
|
+
)
|
|
83
|
+
]
|
|
84
|
+
},
|
|
85
|
+
attachment.stagingId
|
|
86
|
+
);
|
|
87
|
+
}) });
|
|
82
88
|
};
|
|
83
89
|
export {
|
|
84
90
|
AttachmentPreviewList as default
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AttachmentPreviewList.js","sources":["../../../../src/components/Form/attachments/AttachmentPreviewList.tsx"],"sourcesContent":["import classNames from 'classnames';\nimport { useTranslation } from 'react-i18next';\n\nimport ControlledButton from '@/components/Form/ControlledButton';\nimport ControlledImage from '@/components/Form/ControlledImage';\nimport ControlledTooltip from '@/components/UI/ControlledTooltip';\nimport { MaterialSymbol } from '@/components/UI/MaterialSymbol';\nimport { formatFileSize, iconForMimeType } from '@/helpers/fileAttachments';\nimport { IStagedAttachment } from '@/hooks/useFileAttachments';\n\nimport { SymbolCodepoints } from '../../UI/MaterialSymbol';\n\n/**\n * An action offered on a staged IMAGE before it is sent (annotate it, run an AI adjustment). The\n * library does not know what any of them do: it draws the button and hands the staged file back.\n */\nexport interface IStagedImageAction {\n key: string;\n icon: SymbolCodepoints;\n label: string;\n onClick: (attachment: IStagedAttachment) => void;\n hidden?: (attachment: IStagedAttachment) => boolean;\n}\n\ninterface IAttachmentPreviewListProps {\n stagedAttachments: Array<IStagedAttachment>;\n onRemove: (stagingId: string) => void;\n onRetry: (stagingId: string) => void;\n /** Offered only on an image whose upload finished: editing one mid-flight would race the upload. */\n imageActions?: Array<IStagedImageAction>;\n}\n\nconst isEditableImage = (attachment: IStagedAttachment): boolean =>\n attachment.status === 'done' && Boolean(attachment.previewUrl) && attachment.file.type.startsWith('image/');\n\nconst AttachmentPreviewList: React.FC<IAttachmentPreviewListProps> = ({\n stagedAttachments,\n onRemove,\n onRetry,\n imageActions,\n}) => {\n const { t } = useTranslation('ui');\n\n if (!stagedAttachments.length) return null;\n\n return (\n <div className=\"flex flex-wrap gap-2\">\n {stagedAttachments.map((attachment) => (\n <div\n key={attachment.stagingId}\n className={classNames(\n 'relative flex
|
|
1
|
+
{"version":3,"file":"AttachmentPreviewList.js","sources":["../../../../src/components/Form/attachments/AttachmentPreviewList.tsx"],"sourcesContent":["import classNames from 'classnames';\nimport { useTranslation } from 'react-i18next';\n\nimport ControlledButton from '@/components/Form/ControlledButton';\nimport ControlledImage from '@/components/Form/ControlledImage';\nimport ControlledTooltip from '@/components/UI/ControlledTooltip';\nimport { MaterialSymbol } from '@/components/UI/MaterialSymbol';\nimport { formatFileSize, iconForMimeType } from '@/helpers/fileAttachments';\nimport { IStagedAttachment } from '@/hooks/useFileAttachments';\n\nimport { SymbolCodepoints } from '../../UI/MaterialSymbol';\n\n/**\n * An action offered on a staged IMAGE before it is sent (annotate it, run an AI adjustment). The\n * library does not know what any of them do: it draws the button and hands the staged file back.\n */\nexport interface IStagedImageAction {\n key: string;\n icon: SymbolCodepoints;\n label: string;\n onClick: (attachment: IStagedAttachment) => void;\n hidden?: (attachment: IStagedAttachment) => boolean;\n}\n\ninterface IAttachmentPreviewListProps {\n stagedAttachments: Array<IStagedAttachment>;\n onRemove: (stagingId: string) => void;\n onRetry: (stagingId: string) => void;\n /** Offered only on an image whose upload finished: editing one mid-flight would race the upload. */\n imageActions?: Array<IStagedImageAction>;\n}\n\nconst isEditableImage = (attachment: IStagedAttachment): boolean =>\n attachment.status === 'done' && Boolean(attachment.previewUrl) && attachment.file.type.startsWith('image/');\n\nconst AttachmentPreviewList: React.FC<IAttachmentPreviewListProps> = ({\n stagedAttachments,\n onRemove,\n onRetry,\n imageActions,\n}) => {\n const { t } = useTranslation('ui');\n\n if (!stagedAttachments.length) return null;\n\n return (\n <div className=\"flex flex-wrap gap-2\">\n {stagedAttachments.map((attachment) => {\n const actions = isEditableImage(attachment)\n ? (imageActions ?? []).filter((action) => !action.hidden?.(attachment))\n : [];\n\n return (\n <div\n key={attachment.stagingId}\n className={classNames(\n 'relative flex animate-pop-in flex-col overflow-hidden rounded-xl border bg-surface',\n // The buttons sit on the same row as the remove one, so the chip grows to keep the\n // file name readable instead of squeezing it to a couple of characters.\n actions.length > 0 ? 'w-56' : 'w-44',\n attachment.status === 'error' ? 'border-danger-border' : 'border-border',\n )}\n >\n <div className=\"flex items-center gap-x-2 p-2\">\n {attachment.previewUrl && attachment.file.type.startsWith('image/') ? (\n <ControlledImage src={attachment.previewUrl} className=\"size-10 shrink-0 rounded-lg object-cover\" />\n ) : (\n <span className=\"flex size-10 shrink-0 items-center justify-center rounded-lg bg-brand-soft text-brand-emphasis\">\n <MaterialSymbol icon={iconForMimeType(attachment.file.type)} size={20} />\n </span>\n )}\n\n <span className=\"flex min-w-0 flex-1 flex-col\">\n <span className=\"truncate text-xs font-medium text-foreground\">{attachment.file.name}</span>\n <span className=\"text-xxs text-foreground-subtle\">\n {attachment.status === 'error'\n ? t('followUp.attachments.uploadFailed')\n : formatFileSize(attachment.file.size)}\n </span>\n </span>\n\n {actions.map((action) => (\n <ControlledTooltip key={action.key} content={action.label} className=\"z-[9999]\">\n <ControlledButton\n defaultHtmlButton\n onClick={() => action.onClick(attachment)}\n className=\"flex size-6 shrink-0 items-center justify-center rounded-full text-foreground-faint transition-colors hover:bg-muted-hover hover:text-brand-emphasis\"\n >\n <MaterialSymbol icon={action.icon} size={14} />\n </ControlledButton>\n </ControlledTooltip>\n ))}\n\n {attachment.status === 'error' && (\n <ControlledTooltip content={t('actions.tryAgain')} className=\"z-[9999]\">\n <ControlledButton\n defaultHtmlButton\n onClick={() => onRetry(attachment.stagingId)}\n className=\"flex size-6 shrink-0 items-center justify-center rounded-full text-danger-emphasis hover:bg-danger-soft\"\n >\n <MaterialSymbol icon=\"refresh\" size={14} />\n </ControlledButton>\n </ControlledTooltip>\n )}\n\n <ControlledTooltip content={t('actions.remove')} className=\"z-[9999]\">\n <ControlledButton\n defaultHtmlButton\n onClick={() => onRemove(attachment.stagingId)}\n className=\"flex size-6 shrink-0 items-center justify-center rounded-full text-foreground-faint hover:bg-muted-hover hover:text-foreground-muted\"\n >\n <MaterialSymbol icon=\"close\" size={14} />\n </ControlledButton>\n </ControlledTooltip>\n </div>\n\n {attachment.status === 'uploading' && (\n <div\n className=\"h-1 w-full bg-muted-hover\"\n role=\"progressbar\"\n aria-valuemin={0}\n aria-valuemax={100}\n aria-valuenow={attachment.progress}\n aria-label={t('followUp.attachments.uploading')}\n >\n <div\n className=\"h-1 rounded-r-full bg-brand transition-interactive duration-base\"\n style={{ width: `${attachment.progress}%` }}\n />\n </div>\n )}\n </div>\n );\n })}\n </div>\n );\n};\n\nexport default AttachmentPreviewList;\n"],"names":[],"mappings":"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"}
|