@arqel-dev/fields 0.15.0 β 0.16.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/dist/color.js +3 -1
- package/dist/color.js.map +1 -1
- package/dist/date.js +83 -3
- package/dist/date.js.map +1 -1
- package/dist/file.js +8 -5
- package/dist/file.js.map +1 -1
- package/dist/index.js +138 -36
- package/dist/index.js.map +1 -1
- package/dist/number.js +30 -18
- package/dist/number.js.map +1 -1
- package/dist/register.js +138 -36
- package/dist/register.js.map +1 -1
- package/dist/relationship.js +9 -6
- package/dist/relationship.js.map +1 -1
- package/dist/select.js +5 -1
- package/dist/select.js.map +1 -1
- package/dist/slug.js +4 -2
- package/dist/slug.js.map +1 -1
- package/dist/text.js +3 -1
- package/dist/text.js.map +1 -1
- package/package.json +6 -4
package/README.md
CHANGED
|
@@ -2,13 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
[](../../LICENSE)
|
|
4
4
|
[](https://react.dev)
|
|
5
|
-
[](#)
|
|
6
6
|
|
|
7
7
|
Rich React input components for [Arqel](https://arqel.dev), registered into `@arqel-dev/ui`'s FieldRegistry.
|
|
8
8
|
|
|
9
9
|
## Status
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
**Alpha** β 21 field components entregues: text (TextInput, TextareaInput, EmailInput, UrlInput, PasswordInput), number (NumberInput, CurrencyInput), boolean (Checkbox, Toggle), select (SelectInput, MultiSelectInput, RadioGroup), relationship (BelongsToInput, HasManyReadonly), date (DateInput, DateTimeInput), file (FileInput, ImageInput), slug (SlugInput), color (ColorInput) e hidden (HiddenInput).
|
|
12
12
|
|
|
13
13
|
## Install
|
|
14
14
|
|
package/dist/color.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { useArqelTranslations } from '@arqel-dev/react/utils';
|
|
1
2
|
import { cn } from '@arqel-dev/ui/utils';
|
|
2
3
|
import { jsxs, jsx } from 'react/jsx-runtime';
|
|
3
4
|
|
|
@@ -12,6 +13,7 @@ function ColorInput({
|
|
|
12
13
|
describedBy
|
|
13
14
|
}) {
|
|
14
15
|
const f = field;
|
|
16
|
+
const t = useArqelTranslations();
|
|
15
17
|
const hasError = errors !== void 0 && errors.length > 0;
|
|
16
18
|
const isDisabled = disabled || f.disabled || f.readonly;
|
|
17
19
|
const current = typeof value === "string" && value.length > 0 ? value : "#000000";
|
|
@@ -47,7 +49,7 @@ function ColorInput({
|
|
|
47
49
|
"button",
|
|
48
50
|
{
|
|
49
51
|
type: "button",
|
|
50
|
-
"aria-label":
|
|
52
|
+
"aria-label": t("arqel.fields.color.preset_aria", "Preset :color", { color: preset }),
|
|
51
53
|
disabled: isDisabled,
|
|
52
54
|
onClick: () => onChange(preset),
|
|
53
55
|
className: "h-6 w-6 rounded-full border border-border disabled:cursor-not-allowed disabled:opacity-50",
|
package/dist/color.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/color/ColorInput.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"sources":["../src/color/ColorInput.tsx"],"names":[],"mappings":";;;;;AAaO,SAAS,UAAA,CAAW;AAAA,EACzB,KAAA;AAAA,EACA,KAAA;AAAA,EACA,QAAA;AAAA,EACA,MAAA;AAAA,EACA,QAAA;AAAA,EACA,OAAA;AAAA,EACA;AACF,CAAA,EAAuB;AACrB,EAAA,MAAM,CAAA,GAAI,KAAA;AACV,EAAA,MAAM,IAAI,oBAAA,EAAqB;AAC/B,EAAA,MAAM,QAAA,GAAW,MAAA,KAAW,MAAA,IAAa,MAAA,CAAO,MAAA,GAAS,CAAA;AACzD,EAAA,MAAM,UAAA,GAAa,QAAA,IAAY,CAAA,CAAE,QAAA,IAAY,CAAA,CAAE,QAAA;AAC/C,EAAA,MAAM,UAAU,OAAO,KAAA,KAAU,YAAY,KAAA,CAAM,MAAA,GAAS,IAAI,KAAA,GAAQ,SAAA;AACxE,EAAA,MAAM,OAAA,GAAU,CAAA,CAAE,KAAA,CAAM,OAAA,IAAW,EAAC;AAEpC,EAAA,uBACE,IAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAU,mCAAA,EACb,QAAA,EAAA;AAAA,oBAAA,GAAA;AAAA,MAAC,OAAA;AAAA,MAAA;AAAA,QACC,EAAA,EAAI,OAAA;AAAA,QACJ,IAAA,EAAK,OAAA;AAAA,QACL,SAAA,EAAW,EAAA;AAAA,UACT,iEAAA;AAAA,UACA;AAAA,SACF;AAAA,QACA,KAAA,EAAO,OAAA;AAAA,QACP,QAAA,EAAU,UAAA;AAAA,QACV,gBAAc,QAAA,IAAY,MAAA;AAAA,QAC1B,kBAAA,EAAkB,WAAA;AAAA,QAClB,UAAU,CAAC,CAAA,KAAM,QAAA,CAAS,CAAA,CAAE,OAAO,KAAK;AAAA;AAAA,KAC1C;AAAA,oBACA,GAAA;AAAA,MAAC,OAAA;AAAA,MAAA;AAAA,QACC,IAAA,EAAK,MAAA;AAAA,QACL,SAAA,EAAU,uFAAA;AAAA,QACV,KAAA,EAAO,OAAA;AAAA,QACP,QAAA,EAAU,UAAA;AAAA,QACV,UAAU,CAAC,CAAA,KAAM,QAAA,CAAS,CAAA,CAAE,OAAO,KAAK;AAAA;AAAA,KAC1C;AAAA,IACC,OAAA,CAAQ,MAAA,GAAS,CAAA,oBAChB,GAAA,CAAC,KAAA,EAAA,EAAI,WAAU,sBAAA,EACZ,QAAA,EAAA,OAAA,CAAQ,GAAA,CAAI,CAAC,MAAA,qBACZ,GAAA;AAAA,MAAC,QAAA;AAAA,MAAA;AAAA,QAEC,IAAA,EAAK,QAAA;AAAA,QACL,cAAY,CAAA,CAAE,gCAAA,EAAkC,iBAAiB,EAAE,KAAA,EAAO,QAAQ,CAAA;AAAA,QAClF,QAAA,EAAU,UAAA;AAAA,QACV,OAAA,EAAS,MAAM,QAAA,CAAS,MAAM,CAAA;AAAA,QAC9B,SAAA,EAAU,2FAAA;AAAA,QACV,KAAA,EAAO,EAAE,eAAA,EAAiB,MAAA;AAAO,OAAA;AAAA,MAN5B;AAAA,KAQR,CAAA,EACH;AAAA,GAAA,EAEJ,CAAA;AAEJ","file":"color.js","sourcesContent":["/**\n * `<ColorInput>` β native color picker plus optional preset swatches.\n *\n * `field.props.presets: string[]` (hex strings) renders a strip of\n * clickable swatches above the picker. The native `<input type=\"color\">`\n * is a/11y-friendly out of the box.\n */\n\nimport { useArqelTranslations } from '@arqel-dev/react/utils';\nimport type { ColorFieldSchema } from '@arqel-dev/types/fields';\nimport type { FieldRendererProps } from '@arqel-dev/ui/form';\nimport { cn } from '@arqel-dev/ui/utils';\n\nexport function ColorInput({\n field,\n value,\n onChange,\n errors,\n disabled,\n inputId,\n describedBy,\n}: FieldRendererProps) {\n const f = field as ColorFieldSchema;\n const t = useArqelTranslations();\n const hasError = errors !== undefined && errors.length > 0;\n const isDisabled = disabled || f.disabled || f.readonly;\n const current = typeof value === 'string' && value.length > 0 ? value : '#000000';\n const presets = f.props.presets ?? [];\n\n return (\n <div className=\"flex flex-wrap items-center gap-2\">\n <input\n id={inputId}\n type=\"color\"\n className={cn(\n 'h-9 w-12 cursor-pointer rounded-sm border border-[var(--input)]',\n 'disabled:cursor-not-allowed disabled:opacity-50',\n )}\n value={current}\n disabled={isDisabled}\n aria-invalid={hasError || undefined}\n aria-describedby={describedBy}\n onChange={(e) => onChange(e.target.value)}\n />\n <input\n type=\"text\"\n className=\"h-9 w-28 rounded-sm border border-[var(--input)] bg-background px-2 font-mono text-xs\"\n value={current}\n disabled={isDisabled}\n onChange={(e) => onChange(e.target.value)}\n />\n {presets.length > 0 && (\n <div className=\"flex flex-wrap gap-1\">\n {presets.map((preset) => (\n <button\n key={preset}\n type=\"button\"\n aria-label={t('arqel.fields.color.preset_aria', 'Preset :color', { color: preset })}\n disabled={isDisabled}\n onClick={() => onChange(preset)}\n className=\"h-6 w-6 rounded-full border border-border disabled:cursor-not-allowed disabled:opacity-50\"\n style={{ backgroundColor: preset }}\n />\n ))}\n </div>\n )}\n </div>\n );\n}\n"]}
|
package/dist/date.js
CHANGED
|
@@ -45,6 +45,65 @@ function DateInput({
|
|
|
45
45
|
}
|
|
46
46
|
);
|
|
47
47
|
}
|
|
48
|
+
function hasZone(value) {
|
|
49
|
+
return /[zZ]$|[+-]\d{2}:?\d{2}$/.test(value.trim());
|
|
50
|
+
}
|
|
51
|
+
function toZonedLocalInput(date, timeZone, withSeconds) {
|
|
52
|
+
const options = {
|
|
53
|
+
year: "numeric",
|
|
54
|
+
month: "2-digit",
|
|
55
|
+
day: "2-digit",
|
|
56
|
+
hour: "2-digit",
|
|
57
|
+
minute: "2-digit",
|
|
58
|
+
hour12: false,
|
|
59
|
+
...withSeconds ? { second: "2-digit" } : {}
|
|
60
|
+
};
|
|
61
|
+
let parts;
|
|
62
|
+
try {
|
|
63
|
+
parts = new Intl.DateTimeFormat("en-CA", { ...options, timeZone }).formatToParts(date);
|
|
64
|
+
} catch {
|
|
65
|
+
parts = new Intl.DateTimeFormat("en-CA", options).formatToParts(date);
|
|
66
|
+
}
|
|
67
|
+
const get = (type) => parts.find((p) => p.type === type)?.value ?? "00";
|
|
68
|
+
const hour = get("hour") === "24" ? "00" : get("hour");
|
|
69
|
+
const date_ = `${get("year")}-${get("month")}-${get("day")}`;
|
|
70
|
+
const time = withSeconds ? `${hour}:${get("minute")}:${get("second")}` : `${hour}:${get("minute")}`;
|
|
71
|
+
return `${date_}T${time}`;
|
|
72
|
+
}
|
|
73
|
+
function zonedLocalInputToIso(wallClock, timeZone) {
|
|
74
|
+
const asUtc = /* @__PURE__ */ new Date(`${wallClock}Z`);
|
|
75
|
+
if (Number.isNaN(asUtc.getTime())) return wallClock;
|
|
76
|
+
const offsetMs = zoneOffsetMs(asUtc, timeZone);
|
|
77
|
+
return new Date(asUtc.getTime() - offsetMs).toISOString();
|
|
78
|
+
}
|
|
79
|
+
function zoneOffsetMs(date, timeZone) {
|
|
80
|
+
try {
|
|
81
|
+
const dtf = new Intl.DateTimeFormat("en-US", {
|
|
82
|
+
timeZone,
|
|
83
|
+
year: "numeric",
|
|
84
|
+
month: "2-digit",
|
|
85
|
+
day: "2-digit",
|
|
86
|
+
hour: "2-digit",
|
|
87
|
+
minute: "2-digit",
|
|
88
|
+
second: "2-digit",
|
|
89
|
+
hour12: false
|
|
90
|
+
});
|
|
91
|
+
const parts = dtf.formatToParts(date);
|
|
92
|
+
const get = (type) => Number(parts.find((p) => p.type === type)?.value ?? "0");
|
|
93
|
+
const hour = get("hour") === 24 ? 0 : get("hour");
|
|
94
|
+
const asIfUtc = Date.UTC(
|
|
95
|
+
get("year"),
|
|
96
|
+
get("month") - 1,
|
|
97
|
+
get("day"),
|
|
98
|
+
hour,
|
|
99
|
+
get("minute"),
|
|
100
|
+
get("second")
|
|
101
|
+
);
|
|
102
|
+
return asIfUtc - date.getTime();
|
|
103
|
+
} catch {
|
|
104
|
+
return 0;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
48
107
|
function DateTimeInput({
|
|
49
108
|
field,
|
|
50
109
|
value,
|
|
@@ -56,22 +115,43 @@ function DateTimeInput({
|
|
|
56
115
|
}) {
|
|
57
116
|
const f = field;
|
|
58
117
|
const hasError = errors !== void 0 && errors.length > 0;
|
|
118
|
+
const timeZone = f.props.timezone;
|
|
119
|
+
const withSeconds = f.props.seconds === true;
|
|
120
|
+
const raw = typeof value === "string" ? value : "";
|
|
121
|
+
let display = raw;
|
|
122
|
+
if (timeZone !== void 0 && timeZone !== "" && raw !== "" && hasZone(raw)) {
|
|
123
|
+
const parsed = new Date(raw);
|
|
124
|
+
if (!Number.isNaN(parsed.getTime())) {
|
|
125
|
+
display = toZonedLocalInput(parsed, timeZone, withSeconds);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
59
128
|
return /* @__PURE__ */ jsx(
|
|
60
129
|
"input",
|
|
61
130
|
{
|
|
62
131
|
id: inputId,
|
|
63
132
|
type: "datetime-local",
|
|
64
133
|
className: inputClasses,
|
|
65
|
-
value:
|
|
134
|
+
value: display,
|
|
66
135
|
disabled: disabled || f.disabled || f.readonly,
|
|
67
136
|
readOnly: f.readonly === true,
|
|
68
137
|
required: f.required === true,
|
|
69
138
|
min: f.props.minDate,
|
|
70
139
|
max: f.props.maxDate,
|
|
71
|
-
step:
|
|
140
|
+
step: withSeconds ? 1 : void 0,
|
|
72
141
|
"aria-invalid": hasError || void 0,
|
|
73
142
|
"aria-describedby": describedBy,
|
|
74
|
-
onChange: (e) =>
|
|
143
|
+
onChange: (e) => {
|
|
144
|
+
const next = e.target.value;
|
|
145
|
+
if (next === "") {
|
|
146
|
+
onChange(null);
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
if (timeZone !== void 0 && timeZone !== "") {
|
|
150
|
+
onChange(zonedLocalInputToIso(next, timeZone));
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
onChange(next);
|
|
154
|
+
}
|
|
75
155
|
}
|
|
76
156
|
);
|
|
77
157
|
}
|
package/dist/date.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/shared/styles.ts","../src/date/DateInput.tsx","../src/date/DateTimeInput.tsx"],"names":["jsx"],"mappings":";;;;AAOO,IAAM,YAAA,GAAe,EAAA;AAAA,EAC1B,oDAAA;AAAA,EACA,4CAAA;AAAA,EACA,mCAAA;AAAA,EACA,yEAAA;AAAA,EACA,iDAAA;AAAA,EACA;AACF,CAAA;AAE+B,EAAA;AAAA,EAC7B,uCAAA;AAAA,EACA,cAAA;AAAA,EACA,yEAAA;AAAA,EACA;AACF;ACRO,SAAS,SAAA,CAAU;AAAA,EACxB,KAAA;AAAA,EACA,KAAA;AAAA,EACA,QAAA;AAAA,EACA,MAAA;AAAA,EACA,QAAA;AAAA,EACA,OAAA;AAAA,EACA;AACF,CAAA,EAAuB;AACrB,EAAA,MAAM,CAAA,GAAI,KAAA;AACV,EAAA,MAAM,QAAA,GAAW,MAAA,KAAW,MAAA,IAAa,MAAA,CAAO,MAAA,GAAS,CAAA;AACzD,EAAA,uBACE,GAAA;AAAA,IAAC,OAAA;AAAA,IAAA;AAAA,MACC,EAAA,EAAI,OAAA;AAAA,MACJ,IAAA,EAAK,MAAA;AAAA,MACL,SAAA,EAAW,YAAA;AAAA,MACX,KAAA,EAAO,OAAO,KAAA,KAAU,QAAA,GAAW,KAAA,GAAQ,EAAA;AAAA,MAC3C,QAAA,EAAU,QAAA,IAAY,CAAA,CAAE,QAAA,IAAY,CAAA,CAAE,QAAA;AAAA,MACtC,QAAA,EAAU,EAAE,QAAA,KAAa,IAAA;AAAA,MACzB,QAAA,EAAU,EAAE,QAAA,KAAa,IAAA;AAAA,MACzB,GAAA,EAAK,EAAE,KAAA,CAAM,OAAA;AAAA,MACb,GAAA,EAAK,EAAE,KAAA,CAAM,OAAA;AAAA,MACb,gBAAc,QAAA,IAAY,MAAA;AAAA,MAC1B,kBAAA,EAAkB,WAAA;AAAA,MAClB,QAAA,EAAU,CAAC,CAAA,KAAM,QAAA,CAAS,CAAA,CAAE,MAAA,CAAO,KAAA,KAAU,EAAA,GAAK,IAAA,GAAO,CAAA,CAAE,MAAA,CAAO,KAAK;AAAA;AAAA,GACzE;AAEJ;ACpCO,SAAS,aAAA,CAAc;AAAA,EAC5B,KAAA;AAAA,EACA,KAAA;AAAA,EACA,QAAA;AAAA,EACA,MAAA;AAAA,EACA,QAAA;AAAA,EACA,OAAA;AAAA,EACA;AACF,CAAA,EAAuB;AACrB,EAAA,MAAM,CAAA,GAAI,KAAA;AACV,EAAA,MAAM,QAAA,GAAW,MAAA,KAAW,MAAA,IAAa,MAAA,CAAO,MAAA,GAAS,CAAA;AACzD,EAAA,uBACEA,GAAAA;AAAA,IAAC,OAAA;AAAA,IAAA;AAAA,MACC,EAAA,EAAI,OAAA;AAAA,MACJ,IAAA,EAAK,gBAAA;AAAA,MACL,SAAA,EAAW,YAAA;AAAA,MACX,KAAA,EAAO,OAAO,KAAA,KAAU,QAAA,GAAW,KAAA,GAAQ,EAAA;AAAA,MAC3C,QAAA,EAAU,QAAA,IAAY,CAAA,CAAE,QAAA,IAAY,CAAA,CAAE,QAAA;AAAA,MACtC,QAAA,EAAU,EAAE,QAAA,KAAa,IAAA;AAAA,MACzB,QAAA,EAAU,EAAE,QAAA,KAAa,IAAA;AAAA,MACzB,GAAA,EAAK,EAAE,KAAA,CAAM,OAAA;AAAA,MACb,GAAA,EAAK,EAAE,KAAA,CAAM,OAAA;AAAA,MACb,IAAA,EAAM,CAAA,CAAE,KAAA,CAAM,OAAA,GAAU,CAAA,GAAI,MAAA;AAAA,MAC5B,gBAAc,QAAA,IAAY,MAAA;AAAA,MAC1B,kBAAA,EAAkB,WAAA;AAAA,MAClB,QAAA,EAAU,CAAC,CAAA,KAAM,QAAA,CAAS,CAAA,CAAE,MAAA,CAAO,KAAA,KAAU,EAAA,GAAK,IAAA,GAAO,CAAA,CAAE,MAAA,CAAO,KAAK;AAAA;AAAA,GACzE;AAEJ","file":"date.js","sourcesContent":["/**\n * Shared input styling β keeps the visual contract consistent across\n * every Field component without forcing a wrapper component.\n */\n\nimport { cn } from '@arqel-dev/ui/utils';\n\nexport const inputClasses = cn(\n 'h-9 w-full rounded-sm border border-[var(--input)]',\n 'bg-background px-3 text-sm text-foreground',\n 'placeholder:text-muted-foreground',\n 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring',\n 'disabled:cursor-not-allowed disabled:opacity-50',\n 'aria-invalid:border-destructive',\n);\n\nexport const checkboxClasses = cn(\n 'h-4 w-4 rounded border-[var(--input)]',\n 'text-primary',\n 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring',\n 'disabled:cursor-not-allowed disabled:opacity-50',\n);\n","/**\n * `<DateInput>` β native `<input type=\"date\">`.\n *\n * Phase 1 leans on the browser's built-in date picker for footprint\n * reasons. A `react-day-picker`-based custom picker lands in Phase 2\n * when we need locale-specific formatting + min/max enforcement\n * beyond what the native control provides.\n */\n\nimport type { DateFieldSchema } from '@arqel-dev/types/fields';\nimport type { FieldRendererProps } from '@arqel-dev/ui/form';\nimport { inputClasses } from '../shared/styles.js';\n\nexport function DateInput({\n field,\n value,\n onChange,\n errors,\n disabled,\n inputId,\n describedBy,\n}: FieldRendererProps) {\n const f = field as DateFieldSchema;\n const hasError = errors !== undefined && errors.length > 0;\n return (\n <input\n id={inputId}\n type=\"date\"\n className={inputClasses}\n value={typeof value === 'string' ? value : ''}\n disabled={disabled || f.disabled || f.readonly}\n readOnly={f.readonly === true}\n required={f.required === true}\n min={f.props.minDate}\n max={f.props.maxDate}\n aria-invalid={hasError || undefined}\n aria-describedby={describedBy}\n onChange={(e) => onChange(e.target.value === '' ? null : e.target.value)}\n />\n );\n}\n","import type { DateTimeFieldSchema } from '@arqel-dev/types/fields';\nimport type { FieldRendererProps } from '@arqel-dev/ui/form';\nimport { inputClasses } from '../shared/styles.js';\n\nexport function DateTimeInput({\n field,\n value,\n onChange,\n errors,\n disabled,\n inputId,\n describedBy,\n}: FieldRendererProps) {\n const f = field as DateTimeFieldSchema;\n const hasError = errors !== undefined && errors.length > 0;\n return (\n <input\n id={inputId}\n type=\"datetime-local\"\n className={inputClasses}\n value={typeof value === 'string' ? value : ''}\n disabled={disabled || f.disabled || f.readonly}\n readOnly={f.readonly === true}\n required={f.required === true}\n min={f.props.minDate}\n max={f.props.maxDate}\n step={f.props.seconds ? 1 : undefined}\n aria-invalid={hasError || undefined}\n aria-describedby={describedBy}\n onChange={(e) => onChange(e.target.value === '' ? null : e.target.value)}\n />\n );\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/shared/styles.ts","../src/date/DateInput.tsx","../src/date/DateTimeInput.tsx"],"names":["jsx"],"mappings":"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","file":"date.js","sourcesContent":["/**\n * Shared input styling β keeps the visual contract consistent across\n * every Field component without forcing a wrapper component.\n */\n\nimport { cn } from '@arqel-dev/ui/utils';\n\nexport const inputClasses = cn(\n 'h-9 w-full rounded-sm border border-[var(--input)]',\n 'bg-background px-3 text-sm text-foreground',\n 'placeholder:text-muted-foreground',\n 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring',\n 'disabled:cursor-not-allowed disabled:opacity-50',\n 'aria-invalid:border-destructive',\n);\n\nexport const checkboxClasses = cn(\n 'h-4 w-4 rounded border-[var(--input)]',\n 'text-primary',\n 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring',\n 'disabled:cursor-not-allowed disabled:opacity-50',\n);\n","/**\n * `<DateInput>` β native `<input type=\"date\">`.\n *\n * Phase 1 leans on the browser's built-in date picker for footprint\n * reasons. A `react-day-picker`-based custom picker lands in Phase 2\n * when we need locale-specific formatting + min/max enforcement\n * beyond what the native control provides.\n */\n\nimport type { DateFieldSchema } from '@arqel-dev/types/fields';\nimport type { FieldRendererProps } from '@arqel-dev/ui/form';\nimport { inputClasses } from '../shared/styles.js';\n\nexport function DateInput({\n field,\n value,\n onChange,\n errors,\n disabled,\n inputId,\n describedBy,\n}: FieldRendererProps) {\n const f = field as DateFieldSchema;\n const hasError = errors !== undefined && errors.length > 0;\n return (\n <input\n id={inputId}\n type=\"date\"\n className={inputClasses}\n value={typeof value === 'string' ? value : ''}\n disabled={disabled || f.disabled || f.readonly}\n readOnly={f.readonly === true}\n required={f.required === true}\n min={f.props.minDate}\n max={f.props.maxDate}\n aria-invalid={hasError || undefined}\n aria-describedby={describedBy}\n onChange={(e) => onChange(e.target.value === '' ? null : e.target.value)}\n />\n );\n}\n","import type { DateTimeFieldSchema } from '@arqel-dev/types/fields';\nimport type { FieldRendererProps } from '@arqel-dev/ui/form';\nimport { inputClasses } from '../shared/styles.js';\n\n/**\n * Detect whether a value string carries an absolute instant β i.e. an\n * explicit UTC `Z` or a numeric `Β±HH:MM` offset. A bare wall-clock string\n * (`2024-01-02T03:04`) has no zone and is shown as-is.\n */\nfunction hasZone(value: string): boolean {\n return /[zZ]$|[+-]\\d{2}:?\\d{2}$/.test(value.trim());\n}\n\n/**\n * Render an absolute `Date` as the `datetime-local` wall-clock string\n * (`YYYY-MM-DDTHH:mm[:ss]`) observed in `timeZone`. Uses `Intl` parts so\n * the conversion honours DST without pulling in a date library. Falls back\n * to the runtime zone if `Intl` rejects the zone.\n */\nfunction toZonedLocalInput(date: Date, timeZone: string, withSeconds: boolean): string {\n const options: Intl.DateTimeFormatOptions = {\n year: 'numeric',\n month: '2-digit',\n day: '2-digit',\n hour: '2-digit',\n minute: '2-digit',\n hour12: false,\n ...(withSeconds ? { second: '2-digit' } : {}),\n };\n let parts: Intl.DateTimeFormatPart[];\n try {\n parts = new Intl.DateTimeFormat('en-CA', { ...options, timeZone }).formatToParts(date);\n } catch {\n parts = new Intl.DateTimeFormat('en-CA', options).formatToParts(date);\n }\n const get = (type: Intl.DateTimeFormatPartTypes): string =>\n parts.find((p) => p.type === type)?.value ?? '00';\n // `Intl` may emit a literal `24` for midnight in some engines; normalise.\n const hour = get('hour') === '24' ? '00' : get('hour');\n const date_ = `${get('year')}-${get('month')}-${get('day')}`;\n const time = withSeconds\n ? `${hour}:${get('minute')}:${get('second')}`\n : `${hour}:${get('minute')}`;\n return `${date_}T${time}`;\n}\n\n/**\n * Inverse of {@link toZonedLocalInput}: interpret a wall-clock string as a\n * local time in `timeZone` and return the equivalent absolute UTC ISO\n * instant, so the edited value round-trips to the server in the same zone\n * the field declared. Computed by measuring the zone's offset at that\n * instant via `Intl` (DST-aware) and subtracting it.\n */\nfunction zonedLocalInputToIso(wallClock: string, timeZone: string): string {\n // Treat the wall-clock as if it were UTC, then correct by the zone offset.\n const asUtc = new Date(`${wallClock}Z`);\n if (Number.isNaN(asUtc.getTime())) return wallClock;\n const offsetMs = zoneOffsetMs(asUtc, timeZone);\n return new Date(asUtc.getTime() - offsetMs).toISOString();\n}\n\n/** Offset (ms) of `timeZone` from UTC at the given instant, DST-aware. */\nfunction zoneOffsetMs(date: Date, timeZone: string): number {\n try {\n const dtf = new Intl.DateTimeFormat('en-US', {\n timeZone,\n year: 'numeric',\n month: '2-digit',\n day: '2-digit',\n hour: '2-digit',\n minute: '2-digit',\n second: '2-digit',\n hour12: false,\n });\n const parts = dtf.formatToParts(date);\n const get = (type: Intl.DateTimeFormatPartTypes): number =>\n Number(parts.find((p) => p.type === type)?.value ?? '0');\n const hour = get('hour') === 24 ? 0 : get('hour');\n const asIfUtc = Date.UTC(\n get('year'),\n get('month') - 1,\n get('day'),\n hour,\n get('minute'),\n get('second'),\n );\n return asIfUtc - date.getTime();\n } catch {\n return 0;\n }\n}\n\nexport function DateTimeInput({\n field,\n value,\n onChange,\n errors,\n disabled,\n inputId,\n describedBy,\n}: FieldRendererProps) {\n const f = field as DateTimeFieldSchema;\n const hasError = errors !== undefined && errors.length > 0;\n // `timezone` is server-emitted from `DateTimeField->timezone(...)`. When set\n // and the incoming value is an absolute instant, the native datetime-local\n // control β which always renders in the *browser* zone β would otherwise show\n // the wrong wall-clock time. Convert into the declared zone for display and\n // back to a UTC instant on edit so the stored moment is preserved.\n const timeZone = f.props.timezone;\n const withSeconds = f.props.seconds === true;\n\n const raw = typeof value === 'string' ? value : '';\n let display = raw;\n if (timeZone !== undefined && timeZone !== '' && raw !== '' && hasZone(raw)) {\n const parsed = new Date(raw);\n if (!Number.isNaN(parsed.getTime())) {\n display = toZonedLocalInput(parsed, timeZone, withSeconds);\n }\n }\n\n return (\n <input\n id={inputId}\n type=\"datetime-local\"\n className={inputClasses}\n value={display}\n disabled={disabled || f.disabled || f.readonly}\n readOnly={f.readonly === true}\n required={f.required === true}\n min={f.props.minDate}\n max={f.props.maxDate}\n step={withSeconds ? 1 : undefined}\n aria-invalid={hasError || undefined}\n aria-describedby={describedBy}\n onChange={(e) => {\n const next = e.target.value;\n if (next === '') {\n onChange(null);\n return;\n }\n // Re-anchor the edited wall-clock to the declared zone so the value\n // sent back is an absolute instant, not a browser-local string.\n if (timeZone !== undefined && timeZone !== '') {\n onChange(zonedLocalInputToIso(next, timeZone));\n return;\n }\n onChange(next);\n }}\n />\n );\n}\n"]}
|
package/dist/file.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { useArqelTranslations } from '@arqel-dev/react/utils';
|
|
1
2
|
import { cn } from '@arqel-dev/ui/utils';
|
|
2
3
|
import { useState, useEffect } from 'react';
|
|
3
4
|
import { jsxs, jsx } from 'react/jsx-runtime';
|
|
@@ -13,6 +14,7 @@ function FileInput({
|
|
|
13
14
|
describedBy
|
|
14
15
|
}) {
|
|
15
16
|
const f = field;
|
|
17
|
+
const t = useArqelTranslations();
|
|
16
18
|
const hasError = errors !== void 0 && errors.length > 0;
|
|
17
19
|
const isDisabled = disabled || f.disabled || f.readonly;
|
|
18
20
|
const [dragOver, setDragOver] = useState(false);
|
|
@@ -25,7 +27,7 @@ function FileInput({
|
|
|
25
27
|
return /* @__PURE__ */ jsxs(
|
|
26
28
|
"section",
|
|
27
29
|
{
|
|
28
|
-
"aria-label": "File upload",
|
|
30
|
+
"aria-label": t("arqel.fields.file.upload", "File upload"),
|
|
29
31
|
onDragOver: (e) => {
|
|
30
32
|
e.preventDefault();
|
|
31
33
|
if (!isDisabled) setDragOver(true);
|
|
@@ -43,9 +45,9 @@ function FileInput({
|
|
|
43
45
|
isDisabled && "opacity-50"
|
|
44
46
|
),
|
|
45
47
|
children: [
|
|
46
|
-
filename ? /* @__PURE__ */ jsx("span", { className: "font-medium", children: filename }) : /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: "Drag a file here or click to browse" }),
|
|
48
|
+
filename ? /* @__PURE__ */ jsx("span", { className: "font-medium", children: filename }) : /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: t("arqel.fields.file.drop_hint", "Drag a file here or click to browse") }),
|
|
47
49
|
/* @__PURE__ */ jsxs("label", { className: "cursor-pointer text-xs text-primary hover:underline", children: [
|
|
48
|
-
filename ? "Choose another file" : "Browse",
|
|
50
|
+
filename ? t("arqel.fields.file.choose_another", "Choose another file") : t("arqel.fields.file.browse", "Browse"),
|
|
49
51
|
/* @__PURE__ */ jsx(
|
|
50
52
|
"input",
|
|
51
53
|
{
|
|
@@ -73,6 +75,7 @@ function ImageInput({
|
|
|
73
75
|
describedBy
|
|
74
76
|
}) {
|
|
75
77
|
const f = field;
|
|
78
|
+
const t = useArqelTranslations();
|
|
76
79
|
const hasError = errors !== void 0 && errors.length > 0;
|
|
77
80
|
const isDisabled = disabled || f.disabled || f.readonly;
|
|
78
81
|
const [previewUrl, setPreviewUrl] = useState(null);
|
|
@@ -94,7 +97,7 @@ function ImageInput({
|
|
|
94
97
|
"img",
|
|
95
98
|
{
|
|
96
99
|
src: previewUrl,
|
|
97
|
-
alt: "Preview",
|
|
100
|
+
alt: t("arqel.fields.image.preview_alt", "Preview"),
|
|
98
101
|
className: cn("max-h-40 w-auto rounded-sm border border-border")
|
|
99
102
|
}
|
|
100
103
|
),
|
|
@@ -107,7 +110,7 @@ function ImageInput({
|
|
|
107
110
|
isDisabled && "cursor-not-allowed opacity-50"
|
|
108
111
|
),
|
|
109
112
|
children: [
|
|
110
|
-
previewUrl ? "Replace image" : "Choose image",
|
|
113
|
+
previewUrl ? t("arqel.fields.image.replace", "Replace image") : t("arqel.fields.image.choose", "Choose image"),
|
|
111
114
|
/* @__PURE__ */ jsx(
|
|
112
115
|
"input",
|
|
113
116
|
{
|
package/dist/file.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/file/FileInput.tsx","../src/file/ImageInput.tsx"],"names":["useState","jsxs","jsx","cn"],"mappings":"
|
|
1
|
+
{"version":3,"sources":["../src/file/FileInput.tsx","../src/file/ImageInput.tsx"],"names":["useArqelTranslations","useState","jsxs","jsx","cn"],"mappings":";;;;;;AAeO,SAAS,SAAA,CAAU;AAAA,EACxB,KAAA;AAAA,EACA,KAAA;AAAA,EACA,QAAA;AAAA,EACA,MAAA;AAAA,EACA,QAAA;AAAA,EACA,OAAA;AAAA,EACA;AACF,CAAA,EAAuB;AACrB,EAAA,MAAM,CAAA,GAAI,KAAA;AACV,EAAA,MAAM,IAAI,oBAAA,EAAqB;AAC/B,EAAA,MAAM,QAAA,GAAW,MAAA,KAAW,MAAA,IAAa,MAAA,CAAO,MAAA,GAAS,CAAA;AACzD,EAAA,MAAM,UAAA,GAAa,QAAA,IAAY,CAAA,CAAE,QAAA,IAAY,CAAA,CAAE,QAAA;AAC/C,EAAA,MAAM,CAAC,QAAA,EAAU,WAAW,CAAA,GAAI,SAAS,KAAK,CAAA;AAE9C,EAAA,MAAM,IAAA,GAAO,KAAA,YAAiB,IAAA,GAAO,KAAA,GAAQ,IAAA;AAC7C,EAAA,MAAM,WAAW,IAAA,EAAM,IAAA,KAAS,OAAO,KAAA,KAAU,WAAW,KAAA,GAAQ,IAAA,CAAA;AAEpE,EAAA,MAAM,WAAA,GAAc,CAAC,KAAA,KAA2B;AAC9C,IAAA,IAAI,CAAC,KAAA,IAAS,KAAA,CAAM,MAAA,KAAW,CAAA,EAAG;AAClC,IAAA,QAAA,CAAS,KAAA,CAAM,CAAC,CAAC,CAAA;AAAA,EACnB,CAAA;AAEA,EAAA,uBACE,IAAA;AAAA,IAAC,SAAA;AAAA,IAAA;AAAA,MACC,YAAA,EAAY,CAAA,CAAE,0BAAA,EAA4B,aAAa,CAAA;AAAA,MACvD,UAAA,EAAY,CAAC,CAAA,KAAM;AACjB,QAAA,CAAA,CAAE,cAAA,EAAe;AACjB,QAAA,IAAI,CAAC,UAAA,EAAY,WAAA,CAAY,IAAI,CAAA;AAAA,MACnC,CAAA;AAAA,MACA,WAAA,EAAa,MAAM,WAAA,CAAY,KAAK,CAAA;AAAA,MACpC,MAAA,EAAQ,CAAC,CAAA,KAAM;AACb,QAAA,CAAA,CAAE,cAAA,EAAe;AACjB,QAAA,WAAA,CAAY,KAAK,CAAA;AACjB,QAAA,IAAI,CAAC,UAAA,EAAY,WAAA,CAAY,CAAA,CAAE,aAAa,KAAK,CAAA;AAAA,MACnD,CAAA;AAAA,MACA,SAAA,EAAW,EAAA;AAAA,QACT,qGAAA;AAAA,QACA,WAAW,yBAAA,GAA4B,eAAA;AAAA,QACvC,QAAA,IAAY,oBAAA;AAAA,QACZ,UAAA,IAAc;AAAA,OAChB;AAAA,MAEC,QAAA,EAAA;AAAA,QAAA,QAAA,mBACC,GAAA,CAAC,MAAA,EAAA,EAAK,SAAA,EAAU,aAAA,EAAe,QAAA,EAAA,QAAA,EAAS,CAAA,mBAExC,GAAA,CAAC,MAAA,EAAA,EAAK,SAAA,EAAU,uBAAA,EACb,QAAA,EAAA,CAAA,CAAE,6BAAA,EAA+B,qCAAqC,CAAA,EACzE,CAAA;AAAA,wBAEF,IAAA,CAAC,OAAA,EAAA,EAAM,SAAA,EAAU,qDAAA,EACd,QAAA,EAAA;AAAA,UAAA,QAAA,GACG,EAAE,kCAAA,EAAoC,qBAAqB,CAAA,GAC3D,CAAA,CAAE,4BAA4B,QAAQ,CAAA;AAAA,0BAC1C,GAAA;AAAA,YAAC,OAAA;AAAA,YAAA;AAAA,cACC,EAAA,EAAI,OAAA;AAAA,cACJ,IAAA,EAAK,MAAA;AAAA,cACL,SAAA,EAAU,SAAA;AAAA,cACV,QAAA,EAAU,UAAA;AAAA,cACV,gBAAc,QAAA,IAAY,MAAA;AAAA,cAC1B,kBAAA,EAAkB,WAAA;AAAA,cAClB,UAAU,CAAC,CAAA,KAAM,WAAA,CAAY,CAAA,CAAE,OAAO,KAAK;AAAA;AAAA;AAC7C,SAAA,EACF;AAAA;AAAA;AAAA,GACF;AAEJ;ACpEO,SAAS,UAAA,CAAW;AAAA,EACzB,KAAA;AAAA,EACA,KAAA;AAAA,EACA,QAAA;AAAA,EACA,MAAA;AAAA,EACA,QAAA;AAAA,EACA,OAAA;AAAA,EACA;AACF,CAAA,EAAuB;AACrB,EAAA,MAAM,CAAA,GAAI,KAAA;AACV,EAAA,MAAM,IAAIA,oBAAAA,EAAqB;AAC/B,EAAA,MAAM,QAAA,GAAW,MAAA,KAAW,MAAA,IAAa,MAAA,CAAO,MAAA,GAAS,CAAA;AACzD,EAAA,MAAM,UAAA,GAAa,QAAA,IAAY,CAAA,CAAE,QAAA,IAAY,CAAA,CAAE,QAAA;AAE/C,EAAA,MAAM,CAAC,UAAA,EAAY,aAAa,CAAA,GAAIC,SAAwB,IAAI,CAAA;AAEhE,EAAA,SAAA,CAAU,MAAM;AACd,IAAA,IAAI,iBAAiB,IAAA,EAAM;AACzB,MAAA,MAAM,GAAA,GAAM,GAAA,CAAI,eAAA,CAAgB,KAAK,CAAA;AACrC,MAAA,aAAA,CAAc,GAAG,CAAA;AACjB,MAAA,OAAO,MAAM,GAAA,CAAI,eAAA,CAAgB,GAAG,CAAA;AAAA,IACtC;AACA,IAAA,IAAI,OAAO,KAAA,KAAU,QAAA,IAAY,KAAA,CAAM,SAAS,CAAA,EAAG;AACjD,MAAA,aAAA,CAAc,KAAK,CAAA;AACnB,MAAA,OAAO,MAAA;AAAA,IACT;AACA,IAAA,aAAA,CAAc,IAAI,CAAA;AAClB,IAAA,OAAO,MAAA;AAAA,EACT,CAAA,EAAG,CAAC,KAAK,CAAC,CAAA;AAEV,EAAA,uBACEC,IAAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAU,qBAAA,EACZ,QAAA,EAAA;AAAA,IAAA,UAAA,oBACCC,GAAAA;AAAA,MAAC,KAAA;AAAA,MAAA;AAAA,QACC,GAAA,EAAK,UAAA;AAAA,QACL,GAAA,EAAK,CAAA,CAAE,gCAAA,EAAkC,SAAS,CAAA;AAAA,QAClD,SAAA,EAAWC,GAAG,iDAAiD;AAAA;AAAA,KACjE;AAAA,oBAEFF,IAAAA;AAAA,MAAC,OAAA;AAAA,MAAA;AAAA,QACC,SAAA,EAAWE,EAAAA;AAAA,UACT,yGAAA;AAAA,UACA,gBAAA;AAAA,UACA,UAAA,IAAc;AAAA,SAChB;AAAA,QAEC,QAAA,EAAA;AAAA,UAAA,UAAA,GACG,EAAE,4BAAA,EAA8B,eAAe,CAAA,GAC/C,CAAA,CAAE,6BAA6B,cAAc,CAAA;AAAA,0BACjDD,GAAAA;AAAA,YAAC,OAAA;AAAA,YAAA;AAAA,cACC,EAAA,EAAI,OAAA;AAAA,cACJ,IAAA,EAAK,MAAA;AAAA,cACL,MAAA,EAAO,SAAA;AAAA,cACP,SAAA,EAAU,SAAA;AAAA,cACV,QAAA,EAAU,UAAA;AAAA,cACV,gBAAc,QAAA,IAAY,MAAA;AAAA,cAC1B,kBAAA,EAAkB,WAAA;AAAA,cAClB,QAAA,EAAU,CAAC,CAAA,KAAM;AACf,gBAAA,MAAM,IAAA,GAAO,CAAA,CAAE,MAAA,CAAO,KAAA,GAAQ,CAAC,CAAA;AAC/B,gBAAA,IAAI,IAAA,WAAe,IAAI,CAAA;AAAA,cACzB;AAAA;AAAA;AACF;AAAA;AAAA;AACF,GAAA,EACF,CAAA;AAEJ","file":"file.js","sourcesContent":["/**\n * `<FileInput>` β drag-drop file picker.\n *\n * Phase 1 stores the selected `File` object directly in the form\n * state; the actual upload happens server-side via Inertia's\n * multipart-aware form submission. Progress events are exposed\n * via `useArqelForm`'s `progress` field (Inertia native).\n */\n\nimport { useArqelTranslations } from '@arqel-dev/react/utils';\nimport type { FileFieldSchema } from '@arqel-dev/types/fields';\nimport type { FieldRendererProps } from '@arqel-dev/ui/form';\nimport { cn } from '@arqel-dev/ui/utils';\nimport { useState } from 'react';\n\nexport function FileInput({\n field,\n value,\n onChange,\n errors,\n disabled,\n inputId,\n describedBy,\n}: FieldRendererProps) {\n const f = field as FileFieldSchema;\n const t = useArqelTranslations();\n const hasError = errors !== undefined && errors.length > 0;\n const isDisabled = disabled || f.disabled || f.readonly;\n const [dragOver, setDragOver] = useState(false);\n\n const file = value instanceof File ? value : null;\n const filename = file?.name ?? (typeof value === 'string' ? value : null);\n\n const handleFiles = (files: FileList | null) => {\n if (!files || files.length === 0) return;\n onChange(files[0]);\n };\n\n return (\n <section\n aria-label={t('arqel.fields.file.upload', 'File upload')}\n onDragOver={(e) => {\n e.preventDefault();\n if (!isDisabled) setDragOver(true);\n }}\n onDragLeave={() => setDragOver(false)}\n onDrop={(e) => {\n e.preventDefault();\n setDragOver(false);\n if (!isDisabled) handleFiles(e.dataTransfer.files);\n }}\n className={cn(\n 'flex flex-col items-center justify-center gap-2 rounded-sm border-2 border-dashed px-4 py-6 text-sm',\n dragOver ? 'border-primary bg-muted' : 'border-border',\n hasError && 'border-destructive',\n isDisabled && 'opacity-50',\n )}\n >\n {filename ? (\n <span className=\"font-medium\">{filename}</span>\n ) : (\n <span className=\"text-muted-foreground\">\n {t('arqel.fields.file.drop_hint', 'Drag a file here or click to browse')}\n </span>\n )}\n <label className=\"cursor-pointer text-xs text-primary hover:underline\">\n {filename\n ? t('arqel.fields.file.choose_another', 'Choose another file')\n : t('arqel.fields.file.browse', 'Browse')}\n <input\n id={inputId}\n type=\"file\"\n className=\"sr-only\"\n disabled={isDisabled}\n aria-invalid={hasError || undefined}\n aria-describedby={describedBy}\n onChange={(e) => handleFiles(e.target.files)}\n />\n </label>\n </section>\n );\n}\n","/**\n * `<ImageInput>` β image picker with preview.\n *\n * Phase 1 ships preview-only (URL or local `URL.createObjectURL`).\n * Crop / aspect-ratio editor lands in Phase 2 with `react-image-crop`.\n */\n\nimport { useArqelTranslations } from '@arqel-dev/react/utils';\nimport type { ImageFieldSchema } from '@arqel-dev/types/fields';\nimport type { FieldRendererProps } from '@arqel-dev/ui/form';\nimport { cn } from '@arqel-dev/ui/utils';\nimport { useEffect, useState } from 'react';\n\nexport function ImageInput({\n field,\n value,\n onChange,\n errors,\n disabled,\n inputId,\n describedBy,\n}: FieldRendererProps) {\n const f = field as ImageFieldSchema;\n const t = useArqelTranslations();\n const hasError = errors !== undefined && errors.length > 0;\n const isDisabled = disabled || f.disabled || f.readonly;\n\n const [previewUrl, setPreviewUrl] = useState<string | null>(null);\n\n useEffect(() => {\n if (value instanceof File) {\n const url = URL.createObjectURL(value);\n setPreviewUrl(url);\n return () => URL.revokeObjectURL(url);\n }\n if (typeof value === 'string' && value.length > 0) {\n setPreviewUrl(value);\n return undefined;\n }\n setPreviewUrl(null);\n return undefined;\n }, [value]);\n\n return (\n <div className=\"flex flex-col gap-2\">\n {previewUrl && (\n <img\n src={previewUrl}\n alt={t('arqel.fields.image.preview_alt', 'Preview')}\n className={cn('max-h-40 w-auto rounded-sm border border-border')}\n />\n )}\n <label\n className={cn(\n 'inline-flex w-fit cursor-pointer items-center gap-2 rounded-sm border border-border px-3 py-1.5 text-sm',\n 'hover:bg-muted',\n isDisabled && 'cursor-not-allowed opacity-50',\n )}\n >\n {previewUrl\n ? t('arqel.fields.image.replace', 'Replace image')\n : t('arqel.fields.image.choose', 'Choose image')}\n <input\n id={inputId}\n type=\"file\"\n accept=\"image/*\"\n className=\"sr-only\"\n disabled={isDisabled}\n aria-invalid={hasError || undefined}\n aria-describedby={describedBy}\n onChange={(e) => {\n const file = e.target.files?.[0];\n if (file) onChange(file);\n }}\n />\n </label>\n </div>\n );\n}\n"]}
|