@povio/ui 2.2.7 → 2.2.8-rc

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 CHANGED
@@ -31,6 +31,7 @@ This is required for buttons to behave as expected. It is based on [Tailwind Upg
31
31
 
32
32
  In order for Tailwind to also compile classes for @povio/ui package, you need to include @source directive in Tailwind config.
33
33
  In your globals.css add (make sure path is correct):
34
+
34
35
  ```css
35
36
  @source "../../node_modules/@povio/ui/dist";
36
37
  ```
@@ -42,6 +43,7 @@ We are using `react-aria-components` for our UI components including their plugi
42
43
  ### Custom Tailwind plugin
43
44
 
44
45
  We also use a custom tailwind plugin to shorten some selectors inside the @povio/ui package. Install it by adding the following line to your `base.css`.
46
+
45
47
  ```css
46
48
  @plugin "@povio/ui/tw-ui-plugin";
47
49
  ```
@@ -1,17 +1,20 @@
1
+ import { CalendarIcon } from "../../../../assets/icons/Calendar.js";
1
2
  import { UIConfig } from "../../../../config/uiConfig.context.js";
3
+ import { TodayIcon } from "../../../../assets/icons/Today.js";
2
4
  import { Calendar } from "../shared/Calendar.js";
3
5
  import { DatePickerInput } from "../shared/DatePickerInput.js";
4
6
  import { DateTimeDialog } from "../shared/DateTimeDialog.js";
5
7
  import { DateTimeDialogFooter } from "../shared/DateTimeDialogFooter.js";
6
8
  import { FormField } from "../../FormField/FormField.js";
9
+ import { StaticInput } from "../../shared/StaticInput.js";
7
10
  import { TooltipWrapper } from "../../shared/TooltipWrapper.js";
8
11
  import { DateTimeUtils } from "../../../../utils/date-time.utils.js";
9
12
  import { jsx, jsxs } from "react/jsx-runtime";
10
13
  import { clsx } from "clsx";
11
- import { useImperativeHandle, useMemo, useRef } from "react";
14
+ import { useEffect, useImperativeHandle, useMemo, useRef, useState } from "react";
12
15
  import { useDatePicker, useLocale } from "react-aria";
13
16
  import { mergeRefs } from "@react-aria/utils";
14
- import { Controller } from "react-hook-form";
17
+ import { Controller, useWatch } from "react-hook-form";
15
18
  import { createCalendar, getLocalTimeZone, toCalendarDate, today } from "@internationalized/date";
16
19
  import { DateTime } from "luxon";
17
20
  import { useCalendarState, useDatePickerState } from "react-stately";
@@ -151,6 +154,18 @@ var DatePickerBase = (props) => {
151
154
  });
152
155
  };
153
156
  var DatePicker = ({ fullIso = true, minValue, maxValue, ...props }) => {
157
+ const ui = UIConfig.useConfig();
158
+ const { locale } = useLocale();
159
+ const [renderInput, setRenderInput] = useState(!ui.renderStaticInput);
160
+ const [shouldFocus, setShouldFocus] = useState(false);
161
+ const inputRef = useRef(null);
162
+ const watchedValue = "formControl" in props && props.formControl ? useWatch({
163
+ control: props.formControl.control,
164
+ name: props.formControl.name
165
+ }) : props.value;
166
+ let isFormControlDisabled = false;
167
+ if ("formControl" in props && props.formControl) isFormControlDisabled = !!props.formControl.control._options?.disabled;
168
+ const rawValue = watchedValue ?? props.value ?? null;
154
169
  const formatCalendarDate = (calendarDate) => {
155
170
  if (calendarDate === null) return null;
156
171
  if (fullIso) return DateTimeUtils.fromCalendarDateToUTCISO(calendarDate);
@@ -167,6 +182,47 @@ var DatePicker = ({ fullIso = true, minValue, maxValue, ...props }) => {
167
182
  minValue: typeof minValue === "string" ? parseCalendarDate(minValue) : minValue,
168
183
  maxValue: typeof maxValue === "string" ? parseCalendarDate(maxValue) : maxValue
169
184
  };
185
+ useEffect(() => {
186
+ if (!renderInput || !shouldFocus) return;
187
+ requestAnimationFrame(() => {
188
+ ((inputRef.current?.getContainer?.() ?? inputRef.current)?.querySelector("input, button, [tabindex]:not([tabindex='-1'])"))?.focus();
189
+ });
190
+ setShouldFocus(false);
191
+ }, [renderInput, shouldFocus]);
192
+ if (!renderInput) {
193
+ const as = props.as ?? ui.input.as;
194
+ const size = props.size ?? ui.input.size;
195
+ const variant = props.variant ?? ui.input.variant;
196
+ const hideLabel = props.hideLabel ?? ui.input.hideLabel;
197
+ const isHeaderHidden = props.isHeaderHidden || as === "inline" || as === "filter" || as === "floating";
198
+ let isDisabled = !!props.isDisabled;
199
+ if (isFormControlDisabled) isDisabled = true;
200
+ const todayIcon = props.todayIcon ?? ui.dateInput.todayIcon;
201
+ const disableManualEntry = props.disableManualEntry ?? ui.dateInput.disableManualEntry;
202
+ const showDropdown = !props.disableDropdown || disableManualEntry;
203
+ const staticPlaceholder = props.placeholder ?? DateTimeUtils.getDatePlaceholder(locale);
204
+ const staticDateValue = rawValue ? parseCalendarDate(rawValue) : null;
205
+ const displayValue = staticDateValue ? DateTimeUtils.formatCalendarDateLocalized(staticDateValue, locale) : "";
206
+ return /* @__PURE__ */ jsx(StaticInput, {
207
+ ...props,
208
+ onInteract: (focus) => {
209
+ setShouldFocus(focus);
210
+ setRenderInput(true);
211
+ },
212
+ className: clsx("relative inline-flex w-full flex-col text-left", props.className),
213
+ as,
214
+ size,
215
+ variant,
216
+ hideLabel,
217
+ isHeaderHidden,
218
+ isDisabled,
219
+ placeholder: staticPlaceholder,
220
+ displayValue,
221
+ isEmpty: !displayValue,
222
+ leadingVisual: todayIcon ? /* @__PURE__ */ jsx(TodayIcon, { className: "size-6 text-interactive-text-secondary-idle" }) : void 0,
223
+ trailingVisual: showDropdown ? /* @__PURE__ */ jsx(CalendarIcon, { className: "size-6 text-interactive-text-secondary-idle" }) : void 0
224
+ });
225
+ }
170
226
  if ("formControl" in props && props.formControl) {
171
227
  const { formControl, ref, ...innerProps } = props;
172
228
  return /* @__PURE__ */ jsx(Controller, {
@@ -175,7 +231,7 @@ var DatePicker = ({ fullIso = true, minValue, maxValue, ...props }) => {
175
231
  render: ({ field, fieldState: { error, isDirty } }) => /* @__PURE__ */ jsx(DatePickerBase, {
176
232
  ...innerProps,
177
233
  ...dateLimits,
178
- ref: mergeRefs(ref, field.ref),
234
+ ref: mergeRefs(ref, field.ref, inputRef),
179
235
  value: parseCalendarDate(field.value),
180
236
  onChange: (value) => field.onChange(formatCalendarDate(value)),
181
237
  onBlur: field.onBlur,
@@ -188,6 +244,7 @@ var DatePicker = ({ fullIso = true, minValue, maxValue, ...props }) => {
188
244
  return /* @__PURE__ */ jsx(DatePickerBase, {
189
245
  ...props,
190
246
  ...dateLimits,
247
+ ref: mergeRefs(props.ref, inputRef),
191
248
  value: parseCalendarDate(props.value),
192
249
  onChange: (value) => props.onChange?.(formatCalendarDate(value))
193
250
  });
@@ -1,21 +1,24 @@
1
+ import { CalendarIcon } from "../../../../assets/icons/Calendar.js";
1
2
  import { Typography } from "../../../text/Typography/Typography.js";
2
3
  import { UIConfig } from "../../../../config/uiConfig.context.js";
3
4
  import "../../../../config/i18n.js";
5
+ import { TodayIcon } from "../../../../assets/icons/Today.js";
4
6
  import { DatePickerInput } from "../shared/DatePickerInput.js";
5
7
  import { DateTimeDialog } from "../shared/DateTimeDialog.js";
6
8
  import { DateTimeDialogFooter } from "../shared/DateTimeDialogFooter.js";
7
9
  import { FormField } from "../../FormField/FormField.js";
10
+ import { StaticInput } from "../../shared/StaticInput.js";
8
11
  import { TooltipWrapper } from "../../shared/TooltipWrapper.js";
9
12
  import { DateTimeUtils } from "../../../../utils/date-time.utils.js";
10
13
  import { RangeCalendar } from "../shared/RangeCalendar.js";
11
14
  import { jsx, jsxs } from "react/jsx-runtime";
12
15
  import { clsx } from "clsx";
13
- import { useCallback, useImperativeHandle, useMemo, useRef, useState } from "react";
16
+ import { useCallback, useEffect, useImperativeHandle, useMemo, useRef, useState } from "react";
14
17
  import { Button } from "react-aria-components";
15
18
  import { useDateRangePicker, useLocale as useLocale$1 } from "react-aria";
16
19
  import { mergeRefs } from "@react-aria/utils";
17
20
  import { useTranslation } from "react-i18next";
18
- import { Controller } from "react-hook-form";
21
+ import { Controller, useWatch } from "react-hook-form";
19
22
  import { createCalendar, endOfMonth, endOfWeek, endOfYear, getLocalTimeZone, startOfMonth, startOfWeek, startOfYear, toCalendarDate, today } from "@internationalized/date";
20
23
  import { DateTime } from "luxon";
21
24
  import { useDateRangePickerState, useRangeCalendarState } from "react-stately";
@@ -530,6 +533,18 @@ var DateRangePickerBase = (props) => {
530
533
  });
531
534
  };
532
535
  var DateRangePicker = ({ fullIso = true, minValue, maxValue, ...props }) => {
536
+ const ui = UIConfig.useConfig();
537
+ const { locale } = useLocale$1();
538
+ const [renderInput, setRenderInput] = useState(!ui.renderStaticInput);
539
+ const [shouldFocus, setShouldFocus] = useState(false);
540
+ const inputRef = useRef(null);
541
+ const watchedValue = "formControl" in props && props.formControl ? useWatch({
542
+ control: props.formControl.control,
543
+ name: props.formControl.name
544
+ }) : props.value;
545
+ let isFormControlDisabled = false;
546
+ if ("formControl" in props && props.formControl) isFormControlDisabled = !!props.formControl.control._options?.disabled;
547
+ const rawValue = watchedValue ?? props.value ?? null;
533
548
  const formatDateRange = (range) => {
534
549
  if (!range?.start || !range?.end) return null;
535
550
  if (fullIso) return {
@@ -568,6 +583,76 @@ var DateRangePicker = ({ fullIso = true, minValue, maxValue, ...props }) => {
568
583
  minValue: typeof minValue === "string" ? parseCalendarDate(minValue) : minValue,
569
584
  maxValue: typeof maxValue === "string" ? parseCalendarDate(maxValue) : maxValue
570
585
  };
586
+ useEffect(() => {
587
+ if (!renderInput || !shouldFocus) return;
588
+ requestAnimationFrame(() => {
589
+ ((inputRef.current?.getContainer?.() ?? inputRef.current)?.querySelector("input, button, [tabindex]:not([tabindex='-1'])"))?.focus();
590
+ });
591
+ setShouldFocus(false);
592
+ }, [renderInput, shouldFocus]);
593
+ if (!renderInput) {
594
+ const as = props.as ?? ui.input.as;
595
+ const size = props.size ?? ui.input.size;
596
+ const variant = props.variant ?? ui.input.variant;
597
+ const hideLabel = props.hideLabel ?? ui.input.hideLabel;
598
+ const isHeaderHidden = props.isHeaderHidden || as === "inline" || as === "filter" || as === "floating";
599
+ let isDisabled = !!props.isDisabled;
600
+ if (isFormControlDisabled) isDisabled = true;
601
+ const todayIcon = props.todayIcon ?? ui.dateInput.todayIcon;
602
+ const disableManualEntry = props.disableManualEntry ?? ui.dateInput.disableManualEntry;
603
+ const showDropdown = !props.disableDropdown || disableManualEntry;
604
+ const defaultDatePlaceholder = DateTimeUtils.getDatePlaceholder(locale);
605
+ const staticPlaceholder = props.placeholder ?? /* @__PURE__ */ jsxs("span", {
606
+ className: "inline-flex items-center gap-input-gap-input-text-to-elements",
607
+ children: [
608
+ /* @__PURE__ */ jsx("span", { children: defaultDatePlaceholder }),
609
+ /* @__PURE__ */ jsx("span", {
610
+ className: "pointer-events-none select-none",
611
+ children: "–"
612
+ }),
613
+ /* @__PURE__ */ jsx("span", { children: defaultDatePlaceholder })
614
+ ]
615
+ });
616
+ const formatStaticDate = (value) => {
617
+ if (!value) return "";
618
+ const parsedDate = parseCalendarDate(value);
619
+ if (!parsedDate) return "";
620
+ return DateTimeUtils.formatCalendarDateLocalized(parsedDate, locale);
621
+ };
622
+ const start = formatStaticDate(rawValue?.start ?? null);
623
+ const end = formatStaticDate(rawValue?.end ?? null);
624
+ const hasRangeValue = !!(start && end);
625
+ const displayValue = hasRangeValue ? /* @__PURE__ */ jsxs("span", {
626
+ className: "inline-flex items-center gap-input-gap-input-text-to-elements",
627
+ children: [
628
+ /* @__PURE__ */ jsx("span", { children: start }),
629
+ /* @__PURE__ */ jsx("span", {
630
+ className: "pointer-events-none select-none",
631
+ children: "–"
632
+ }),
633
+ /* @__PURE__ */ jsx("span", { children: end })
634
+ ]
635
+ }) : void 0;
636
+ return /* @__PURE__ */ jsx(StaticInput, {
637
+ ...props,
638
+ onInteract: (focus) => {
639
+ setShouldFocus(focus);
640
+ setRenderInput(true);
641
+ },
642
+ className: clsx("relative inline-flex w-full flex-col text-left", props.className),
643
+ as,
644
+ size,
645
+ variant,
646
+ hideLabel,
647
+ isHeaderHidden,
648
+ isDisabled,
649
+ placeholder: staticPlaceholder,
650
+ displayValue,
651
+ isEmpty: !hasRangeValue,
652
+ leadingVisual: todayIcon ? /* @__PURE__ */ jsx(TodayIcon, { className: "size-6 text-interactive-text-secondary-idle" }) : void 0,
653
+ trailingVisual: showDropdown ? /* @__PURE__ */ jsx(CalendarIcon, { className: "size-6 text-interactive-text-secondary-idle" }) : void 0
654
+ });
655
+ }
571
656
  if ("formControl" in props && props.formControl) {
572
657
  const { formControl, ref, ...innerProps } = props;
573
658
  return /* @__PURE__ */ jsx(Controller, {
@@ -576,7 +661,7 @@ var DateRangePicker = ({ fullIso = true, minValue, maxValue, ...props }) => {
576
661
  render: ({ field, fieldState: { error, isDirty } }) => /* @__PURE__ */ jsx(DateRangePickerBase, {
577
662
  ...innerProps,
578
663
  ...dateLimits,
579
- ref: mergeRefs(ref, field.ref),
664
+ ref: mergeRefs(ref, field.ref, inputRef),
580
665
  value: parseDateRange(field.value),
581
666
  onChange: (value) => field.onChange(formatDateRange(value)),
582
667
  onBlur: field.onBlur,
@@ -589,6 +674,7 @@ var DateRangePicker = ({ fullIso = true, minValue, maxValue, ...props }) => {
589
674
  return /* @__PURE__ */ jsx(DateRangePickerBase, {
590
675
  ...props,
591
676
  ...dateLimits,
677
+ ref: mergeRefs(props.ref, inputRef),
592
678
  value: parseDateRange(props.value),
593
679
  onChange: (value) => props.onChange?.(formatDateRange(value))
594
680
  });
@@ -1,16 +1,19 @@
1
+ import { DateTimeIcon } from "../../../../assets/icons/DateTime.js";
1
2
  import { UIConfig } from "../../../../config/uiConfig.context.js";
3
+ import { TodayIcon } from "../../../../assets/icons/Today.js";
2
4
  import { Calendar } from "../shared/Calendar.js";
3
5
  import { DatePickerInput } from "../shared/DatePickerInput.js";
4
6
  import { DateTimeDialog } from "../shared/DateTimeDialog.js";
5
7
  import { DateTimeDialogFooter } from "../shared/DateTimeDialogFooter.js";
6
8
  import { FormField } from "../../FormField/FormField.js";
9
+ import { StaticInput } from "../../shared/StaticInput.js";
7
10
  import { TooltipWrapper } from "../../shared/TooltipWrapper.js";
8
11
  import { DateTimeUtils } from "../../../../utils/date-time.utils.js";
9
12
  import { jsx, jsxs } from "react/jsx-runtime";
10
- import { useEffect, useImperativeHandle, useMemo, useRef } from "react";
13
+ import { useEffect, useImperativeHandle, useMemo, useRef, useState } from "react";
11
14
  import { useDatePicker, useLocale } from "react-aria";
12
15
  import { mergeRefs } from "@react-aria/utils";
13
- import { Controller } from "react-hook-form";
16
+ import { Controller, useWatch } from "react-hook-form";
14
17
  import { Time, createCalendar, getLocalTimeZone, now, toCalendarDateTime, today } from "@internationalized/date";
15
18
  import { useCalendarState, useDatePickerState } from "react-stately";
16
19
  //#region src/components/inputs/DateTime/DateTimePicker/DateTimePicker.tsx
@@ -174,6 +177,18 @@ var DateTimePickerBase = (props) => {
174
177
  });
175
178
  };
176
179
  var DateTimePicker = ({ fullIso = true, ...props }) => {
180
+ const ui = UIConfig.useConfig();
181
+ const { locale } = useLocale();
182
+ const [renderInput, setRenderInput] = useState(!ui.renderStaticInput);
183
+ const [shouldFocus, setShouldFocus] = useState(false);
184
+ const inputRef = useRef(null);
185
+ const watchedValue = "formControl" in props && props.formControl ? useWatch({
186
+ control: props.formControl.control,
187
+ name: props.formControl.name
188
+ }) : props.value;
189
+ let isFormControlDisabled = false;
190
+ if ("formControl" in props && props.formControl) isFormControlDisabled = !!props.formControl.control._options?.disabled;
191
+ const rawValue = watchedValue ?? props.value ?? null;
177
192
  const formatDateValue = (dateValue) => {
178
193
  if (dateValue === null) return null;
179
194
  if (fullIso) return DateTimeUtils.fromCalendarDateTimeToUTCISO(dateValue);
@@ -183,6 +198,47 @@ var DateTimePicker = ({ fullIso = true, ...props }) => {
183
198
  if (isoString == null) return isoString;
184
199
  return DateTimeUtils.fromUTCISOToCalendarDateTime(isoString);
185
200
  };
201
+ useEffect(() => {
202
+ if (!renderInput || !shouldFocus) return;
203
+ requestAnimationFrame(() => {
204
+ ((inputRef.current?.getContainer?.() ?? inputRef.current)?.querySelector("input, button, [tabindex]:not([tabindex='-1'])"))?.focus();
205
+ });
206
+ setShouldFocus(false);
207
+ }, [renderInput, shouldFocus]);
208
+ if (!renderInput) {
209
+ const as = props.as ?? ui.input.as;
210
+ const size = props.size ?? ui.input.size;
211
+ const variant = props.variant ?? ui.input.variant;
212
+ const hideLabel = props.hideLabel ?? ui.input.hideLabel;
213
+ const isHeaderHidden = props.isHeaderHidden || as === "inline" || as === "filter" || as === "floating";
214
+ let isDisabled = !!props.isDisabled;
215
+ if (isFormControlDisabled) isDisabled = true;
216
+ const todayIcon = props.todayIcon ?? ui.dateInput.todayIcon;
217
+ const disableManualEntry = props.disableManualEntry ?? ui.dateInput.disableManualEntry;
218
+ const showDropdown = !props.disableDropdown || disableManualEntry;
219
+ const staticPlaceholder = props.placeholder ?? DateTimeUtils.getDateTimePlaceholder(locale);
220
+ const staticDateTimeValue = rawValue ? parseDateValue(rawValue) : null;
221
+ const displayValue = staticDateTimeValue && "hour" in staticDateTimeValue ? DateTimeUtils.formatCalendarDateTimeLocalized(staticDateTimeValue, locale) : "";
222
+ return /* @__PURE__ */ jsx(StaticInput, {
223
+ ...props,
224
+ onInteract: (focus) => {
225
+ setShouldFocus(focus);
226
+ setRenderInput(true);
227
+ },
228
+ className: props.className ? `relative inline-flex w-full flex-col text-left ${props.className}` : "relative inline-flex w-full flex-col text-left",
229
+ as,
230
+ size,
231
+ variant,
232
+ hideLabel,
233
+ isHeaderHidden,
234
+ isDisabled,
235
+ placeholder: staticPlaceholder,
236
+ displayValue,
237
+ isEmpty: !displayValue,
238
+ leadingVisual: todayIcon ? /* @__PURE__ */ jsx(TodayIcon, { className: "size-6 text-interactive-text-secondary-idle" }) : void 0,
239
+ trailingVisual: showDropdown ? /* @__PURE__ */ jsx(DateTimeIcon, { className: "size-6 text-interactive-text-secondary-idle" }) : void 0
240
+ });
241
+ }
186
242
  if ("formControl" in props && props.formControl) {
187
243
  const { formControl, ref, ...innerProps } = props;
188
244
  return /* @__PURE__ */ jsx(Controller, {
@@ -190,7 +246,7 @@ var DateTimePicker = ({ fullIso = true, ...props }) => {
190
246
  name: formControl.name,
191
247
  render: ({ field, fieldState: { error, isDirty } }) => /* @__PURE__ */ jsx(DateTimePickerBase, {
192
248
  ...innerProps,
193
- ref: mergeRefs(ref, field.ref),
249
+ ref: mergeRefs(ref, field.ref, inputRef),
194
250
  value: parseDateValue(field.value),
195
251
  onChange: (value) => field.onChange(formatDateValue(value)),
196
252
  onBlur: field.onBlur,
@@ -202,6 +258,7 @@ var DateTimePicker = ({ fullIso = true, ...props }) => {
202
258
  }
203
259
  return /* @__PURE__ */ jsx(DateTimePickerBase, {
204
260
  ...props,
261
+ ref: mergeRefs(props.ref, inputRef),
205
262
  value: parseDateValue(props.value),
206
263
  onChange: (value) => props.onChange?.(formatDateValue(value))
207
264
  });
@@ -12,6 +12,7 @@ interface TimePickerBaseProps extends FormFieldProps, InputVariantProps, Omit<Ar
12
12
  isDirty?: boolean;
13
13
  disableManualEntry?: boolean;
14
14
  placeholder?: string;
15
+ inputClassName?: string;
15
16
  }
16
17
  export interface TimePickerProps extends Omit<TimePickerBaseProps, "value" | "onChange"> {
17
18
  value?: string | null;
@@ -1,23 +1,26 @@
1
+ import { ClockIcon } from "../../../../assets/icons/Clock.js";
1
2
  import { UIConfig } from "../../../../config/uiConfig.context.js";
2
3
  import { TimePickerForm } from "../shared/TimePickerForm.js";
3
4
  import { DateTimeDialog } from "../shared/DateTimeDialog.js";
4
5
  import { DateTimeDialogFooter } from "../shared/DateTimeDialogFooter.js";
5
6
  import { FormField } from "../../FormField/FormField.js";
7
+ import { StaticInput } from "../../shared/StaticInput.js";
6
8
  import { TooltipWrapper } from "../../shared/TooltipWrapper.js";
7
9
  import { DateTimeUtils } from "../../../../utils/date-time.utils.js";
8
10
  import { TimePickerInput } from "../shared/TimePickerInput.js";
9
11
  import { jsx, jsxs } from "react/jsx-runtime";
12
+ import { clsx } from "clsx";
10
13
  import { useEffect, useRef, useState } from "react";
11
14
  import { useLocale, useTimeField } from "react-aria";
12
15
  import { mergeRefs } from "@react-aria/utils";
13
- import { Controller } from "react-hook-form";
16
+ import { Controller, useWatch } from "react-hook-form";
14
17
  import { getLocalTimeZone, now, toTime } from "@internationalized/date";
15
18
  import { DateTime } from "luxon";
16
19
  import { useTimeFieldState } from "react-stately";
17
20
  //#region src/components/inputs/DateTime/TimePicker/TimePicker.tsx
18
21
  var TimePickerBase = (props) => {
19
22
  const ui = UIConfig.useConfig();
20
- const { ref, label, tooltipText, helperText, isRequired, rightContent, isDisabled, isDirty, headerClassName, errorClassName, isHeaderHidden, error, onChange, value, disableDropdown, placeholder, variant = ui.input.variant, as = ui.input.as, size = ui.input.size, hideLabel = ui.input.hideLabel, isClearable = ui.input.isClearable, disableManualEntry = ui.dateInput.disableManualEntry, ...rest } = props;
23
+ const { ref, label, tooltipText, helperText, isRequired, rightContent, isDisabled, isDirty, headerClassName, errorClassName, isHeaderHidden, error, onChange, value, disableDropdown, placeholder, className, inputClassName, variant = ui.input.variant, as = ui.input.as, size = ui.input.size, hideLabel = ui.input.hideLabel, isClearable = ui.input.isClearable, disableManualEntry = ui.dateInput.disableManualEntry, ...rest } = props;
21
24
  const formFieldProps = {
22
25
  error,
23
26
  label,
@@ -90,7 +93,7 @@ var TimePickerBase = (props) => {
90
93
  ...formFieldProps,
91
94
  as,
92
95
  labelProps,
93
- className: "relative inline-flex w-full flex-col text-left",
96
+ className: clsx("relative inline-flex w-full flex-col text-left", className),
94
97
  tabIndex: as === "inline" ? -1 : void 0,
95
98
  children: [/* @__PURE__ */ jsx(TimePickerInput, {
96
99
  ref: mergeRefs(ref, timeFieldRef),
@@ -107,7 +110,8 @@ var TimePickerBase = (props) => {
107
110
  isClearable,
108
111
  headerProps,
109
112
  disableManualEntry,
110
- placeholder
113
+ placeholder,
114
+ className: inputClassName
111
115
  }), (!disableDropdown || disableManualEntry) && /* @__PURE__ */ jsx(DateTimeDialog, {
112
116
  footer: /* @__PURE__ */ jsx(DateTimeDialogFooter, {
113
117
  isDisabled,
@@ -124,6 +128,18 @@ var TimePickerBase = (props) => {
124
128
  });
125
129
  };
126
130
  var TimePicker = (props) => {
131
+ const ui = UIConfig.useConfig();
132
+ const { locale } = useLocale();
133
+ const [renderInput, setRenderInput] = useState(!ui.renderStaticInput);
134
+ const [shouldFocus, setShouldFocus] = useState(false);
135
+ const inputRef = useRef(null);
136
+ const watchedValue = "formControl" in props && props.formControl ? useWatch({
137
+ control: props.formControl.control,
138
+ name: props.formControl.name
139
+ }) : props.value;
140
+ let isFormControlDisabled = false;
141
+ if ("formControl" in props && props.formControl) isFormControlDisabled = !!props.formControl.control._options?.disabled;
142
+ const rawValue = watchedValue ?? props.value ?? null;
127
143
  const formatTimeValue = (timeValue) => {
128
144
  if (timeValue === null) return null;
129
145
  const parsedDate = props.date ? DateTime.fromISO(props.date).toJSDate() : void 0;
@@ -134,6 +150,45 @@ var TimePicker = (props) => {
134
150
  if (isoString == null) return isoString;
135
151
  return toTime(DateTimeUtils.fromISOtoZonedDateTime(isoString));
136
152
  };
153
+ useEffect(() => {
154
+ if (!renderInput || !shouldFocus) return;
155
+ requestAnimationFrame(() => {
156
+ (inputRef.current?.querySelector("input, button, [tabindex]:not([tabindex='-1'])"))?.focus();
157
+ });
158
+ setShouldFocus(false);
159
+ }, [renderInput, shouldFocus]);
160
+ if (!renderInput) {
161
+ const as = props.as ?? ui.input.as;
162
+ const size = props.size ?? ui.input.size;
163
+ const variant = props.variant ?? ui.input.variant;
164
+ const hideLabel = props.hideLabel ?? ui.input.hideLabel;
165
+ const isHeaderHidden = props.isHeaderHidden || as === "inline" || as === "filter" || as === "floating";
166
+ let isDisabled = !!props.isDisabled;
167
+ if (isFormControlDisabled) isDisabled = true;
168
+ const disableManualEntry = props.disableManualEntry ?? ui.dateInput.disableManualEntry;
169
+ const showDropdown = !props.disableDropdown || disableManualEntry;
170
+ const staticPlaceholder = props.placeholder ?? DateTimeUtils.getTimePlaceholder(locale);
171
+ const staticTimeValue = rawValue ? parseTimeValue(rawValue) : null;
172
+ const displayValue = staticTimeValue ? DateTimeUtils.formatTimeLocalized(staticTimeValue, locale) : "";
173
+ return /* @__PURE__ */ jsx(StaticInput, {
174
+ ...props,
175
+ onInteract: (focus) => {
176
+ setShouldFocus(focus);
177
+ setRenderInput(true);
178
+ },
179
+ className: clsx("relative inline-flex w-full flex-col text-left", props.className),
180
+ as,
181
+ size,
182
+ variant,
183
+ hideLabel,
184
+ isHeaderHidden,
185
+ isDisabled,
186
+ placeholder: staticPlaceholder,
187
+ displayValue,
188
+ isEmpty: !displayValue,
189
+ trailingVisual: showDropdown ? /* @__PURE__ */ jsx(ClockIcon, { className: "size-6 text-interactive-text-secondary-idle" }) : void 0
190
+ });
191
+ }
137
192
  if ("formControl" in props && props.formControl) {
138
193
  const { formControl, ref, ...innerProps } = props;
139
194
  return /* @__PURE__ */ jsx(Controller, {
@@ -141,7 +196,7 @@ var TimePicker = (props) => {
141
196
  name: formControl.name,
142
197
  render: ({ field, fieldState: { error } }) => /* @__PURE__ */ jsx(TimePickerBase, {
143
198
  ...innerProps,
144
- ref: mergeRefs(ref, field.ref),
199
+ ref: mergeRefs(ref, field.ref, inputRef),
145
200
  value: parseTimeValue(field.value),
146
201
  onChange: (value) => field.onChange(formatTimeValue(value)),
147
202
  onBlur: field.onBlur,
@@ -152,6 +207,7 @@ var TimePicker = (props) => {
152
207
  }
153
208
  return /* @__PURE__ */ jsx(TimePickerBase, {
154
209
  ...props,
210
+ ref: mergeRefs(props.ref, inputRef),
155
211
  value: parseTimeValue(props.value),
156
212
  onChange: (value) => props.onChange?.(formatTimeValue(value))
157
213
  });
@@ -15,7 +15,8 @@ interface DatePickerInputProps extends InputVariantProps {
15
15
  isClearable?: boolean;
16
16
  disableManualEntry?: boolean;
17
17
  placeholder?: string;
18
+ className?: string;
18
19
  onPress: () => void;
19
20
  }
20
- export declare const TimePickerInput: ({ ref, as, fieldProps, state, isDisabled, isDirty, isInvalid, disableDropdown, variant, size, isClearable, headerProps, disableManualEntry, placeholder, onPress, ...props }: DatePickerInputProps) => import("react/jsx-runtime").JSX.Element;
21
+ export declare const TimePickerInput: ({ ref, as, fieldProps, state, isDisabled, isDirty, isInvalid, disableDropdown, variant, size, isClearable, headerProps, disableManualEntry, placeholder, className, onPress, ...props }: DatePickerInputProps) => import("react/jsx-runtime").JSX.Element;
21
22
  export {};
@@ -12,7 +12,7 @@ import { useState } from "react";
12
12
  import { Button } from "react-aria-components";
13
13
  import { useFocusVisible, useFocusWithin, useHover } from "react-aria";
14
14
  //#region src/components/inputs/DateTime/shared/TimePickerInput.tsx
15
- var TimePickerInput = ({ ref, as, fieldProps, state, isDisabled, isDirty, isInvalid, disableDropdown, variant, size, isClearable, headerProps, disableManualEntry, placeholder, onPress, ...props }) => {
15
+ var TimePickerInput = ({ ref, as, fieldProps, state, isDisabled, isDirty, isInvalid, disableDropdown, variant, size, isClearable, headerProps, disableManualEntry, placeholder, className, onPress, ...props }) => {
16
16
  const inputBaseCva = UIStyle.useCva("input.baseCva", inputBase);
17
17
  const inputSizeCva = UIStyle.useCva("input.sizeCva", inputSize);
18
18
  const { hoverProps, isHovered } = useHover({ isDisabled });
@@ -23,12 +23,11 @@ var TimePickerInput = ({ ref, as, fieldProps, state, isDisabled, isDirty, isInva
23
23
  const canClear = state.segments.some((segment) => segment.type !== "literal" && segment.isPlaceholder === false);
24
24
  return /* @__PURE__ */ jsxs("div", {
25
25
  ref,
26
- className: inputBaseCva({
26
+ className: clsx(inputBaseCva({
27
27
  variant,
28
28
  as,
29
- ...props,
30
- className: clsx("group/date-picker-content relative min-w-input-width-min-width", "flex items-center justify-between gap-input-gap-input-text-to-elements")
31
- }),
29
+ ...props
30
+ }), "group/date-picker-content relative min-w-input-width-min-width", "flex items-center justify-between gap-input-gap-input-text-to-elements", className),
32
31
  "data-rac": "",
33
32
  "data-datetime-input": "",
34
33
  "data-hovered": isHovered || void 0,
@@ -13,6 +13,7 @@ interface NumberInputBaseProps extends FormFieldProps, InputVariantProps, Omit<I
13
13
  value?: number | null;
14
14
  onChange?: (value: number | null) => void;
15
15
  onBlur?: (e: FocusEvent<HTMLInputElement>) => void;
16
+ autoFocusOnMount?: boolean;
16
17
  }
17
18
  export type NumberInputProps = NumberInputBaseProps;
18
19
  export type ControlledNumberInputProps<TFieldValues extends FieldValues> = ControlProps<NumberInputProps, TFieldValues>;
@@ -1,23 +1,25 @@
1
1
  import { UIStyle } from "../../../../config/uiStyle.context.js";
2
+ import { Loader } from "../../../status/Loader/Loader.js";
2
3
  import { UIConfig } from "../../../../config/uiConfig.context.js";
3
4
  import { inputBase } from "../../shared/input.cva.js";
4
5
  import { FormField } from "../../FormField/FormField.js";
6
+ import { StaticInput } from "../../shared/StaticInput.js";
5
7
  import { TooltipWrapper } from "../../shared/TooltipWrapper.js";
6
8
  import { InputContent } from "../shared/InputContent.js";
7
- import { jsx } from "react/jsx-runtime";
9
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
8
10
  import { clsx } from "clsx";
9
- import { useCallback, useRef } from "react";
11
+ import { isValidElement, useCallback, useEffect, useRef, useState } from "react";
10
12
  import { Input, useLocale } from "react-aria-components";
11
13
  import { useFocusVisible, useNumberField } from "react-aria";
12
14
  import { mergeRefs } from "@react-aria/utils";
13
- import { Controller } from "react-hook-form";
15
+ import { Controller, useWatch } from "react-hook-form";
14
16
  import { useNumberFieldState } from "react-stately";
15
17
  //#region src/components/inputs/Input/NumberInput/NumberInput.tsx
16
18
  var NumberInputBase = (props) => {
17
19
  const ui = UIConfig.useConfig();
18
20
  const inputBaseCva = UIStyle.useCva("input.baseCva", inputBase);
19
21
  const inputRef = useRef(null);
20
- const { ref, inputClassName, label, tooltipText, helperText, isRequired, rightContent, isDisabled, isDirty, headerClassName, errorClassName, isHeaderHidden, error, className, unit, isLoading, action, leadingIcon, trailingIcon, value, onChange, onBlur, formatOptions = ui.numberInput.formatOptions, variant = ui.input.variant, as = ui.input.as, size = ui.input.size, hideLabel = ui.input.hideLabel, isClearable = ui.input.isClearable, ...rest } = props;
22
+ const { ref, inputClassName, label, tooltipText, helperText, isRequired, rightContent, isDisabled, isDirty, headerClassName, errorClassName, isHeaderHidden, error, className, unit, isLoading, action, leadingIcon, trailingIcon, value, onChange, onBlur, formatOptions = ui.numberInput.formatOptions, variant = ui.input.variant, as = ui.input.as, size = ui.input.size, hideLabel = ui.input.hideLabel, isClearable = ui.input.isClearable, autoFocusOnMount, ...rest } = props;
21
23
  const numberFieldRef = useRef(null);
22
24
  const { isFocusVisible } = useFocusVisible();
23
25
  const { locale } = useLocale();
@@ -34,6 +36,10 @@ var NumberInputBase = (props) => {
34
36
  formatOptions
35
37
  };
36
38
  const { labelProps, inputProps } = useNumberField(numberProps, useNumberFieldState(numberProps), numberFieldRef);
39
+ useEffect(() => {
40
+ if (!autoFocusOnMount || isDisabled) return;
41
+ requestAnimationFrame(() => inputRef.current?.focus());
42
+ }, [autoFocusOnMount, isDisabled]);
37
43
  const formFieldProps = {
38
44
  error,
39
45
  label,
@@ -113,7 +119,68 @@ var NumberInputBase = (props) => {
113
119
  })
114
120
  });
115
121
  };
122
+ var renderIconVisual = (icon) => {
123
+ if (!icon) return null;
124
+ if (isValidElement(icon)) return icon;
125
+ return /* @__PURE__ */ jsx(icon, { className: "size-6 text-interactive-text-secondary-idle" });
126
+ };
116
127
  var NumberInput = (props) => {
128
+ const ui = UIConfig.useConfig();
129
+ const [renderInput, setRenderInput] = useState(!ui.renderStaticInput);
130
+ const [shouldFocus, setShouldFocus] = useState(false);
131
+ const inputRef = useRef(null);
132
+ const watchedValue = "formControl" in props && props.formControl ? useWatch({
133
+ control: props.formControl.control,
134
+ name: props.formControl.name
135
+ }) : props.value;
136
+ let isFormControlDisabled = false;
137
+ if ("formControl" in props && props.formControl) isFormControlDisabled = !!props.formControl.control._options?.disabled;
138
+ useEffect(() => {
139
+ if (!renderInput || !shouldFocus) return;
140
+ requestAnimationFrame(() => inputRef.current?.focus());
141
+ setShouldFocus(false);
142
+ }, [renderInput, shouldFocus]);
143
+ if (!renderInput) {
144
+ const staticValue = watchedValue ?? props.value ?? props.defaultValue;
145
+ const displayValue = staticValue == null ? "" : `${staticValue}`;
146
+ const hasValue = displayValue !== "";
147
+ const as = props.as ?? ui.input.as;
148
+ const size = props.size ?? ui.input.size;
149
+ const variant = props.variant ?? ui.input.variant;
150
+ const hideLabel = props.hideLabel ?? ui.input.hideLabel;
151
+ const isHeaderHidden = props.isHeaderHidden || as === "inline" || as === "filter" || as === "floating";
152
+ let isDisabled = !!props.isDisabled;
153
+ if (isFormControlDisabled) isDisabled = true;
154
+ const trailingVisual = !!props.unit || !!props.isLoading || !!props.action && !props.isLoading || !!props.trailingIcon && !props.isLoading && !props.action ? /* @__PURE__ */ jsxs(Fragment, { children: [
155
+ props.unit && /* @__PURE__ */ jsx("span", {
156
+ className: "text-label-2 text-text-default-3",
157
+ children: props.unit
158
+ }),
159
+ props.isLoading && /* @__PURE__ */ jsx(Loader, {}),
160
+ !props.isLoading && props.action && renderIconVisual(props.action.icon),
161
+ !props.isLoading && !props.action && props.trailingIcon && renderIconVisual(props.trailingIcon)
162
+ ] }) : void 0;
163
+ return /* @__PURE__ */ jsx(StaticInput, {
164
+ ...props,
165
+ onInteract: (focus) => {
166
+ setShouldFocus(focus);
167
+ setRenderInput(true);
168
+ },
169
+ as,
170
+ size,
171
+ variant,
172
+ hideLabel,
173
+ isHeaderHidden,
174
+ isDisabled,
175
+ className: clsx("group w-full", as === "inline" && "h-full", props.className),
176
+ inputClassName: props.inputClassName,
177
+ placeholder: as === "floating" ? "" : props.placeholder,
178
+ displayValue,
179
+ isEmpty: !hasValue,
180
+ leadingVisual: renderIconVisual(props.leadingIcon),
181
+ trailingVisual
182
+ });
183
+ }
117
184
  if ("formControl" in props && props.formControl) {
118
185
  const { formControl, ref, ...innerProps } = props;
119
186
  return /* @__PURE__ */ jsx(Controller, {
@@ -121,17 +188,22 @@ var NumberInput = (props) => {
121
188
  name: formControl.name,
122
189
  render: ({ field, fieldState: { error, isDirty } }) => /* @__PURE__ */ jsx(NumberInputBase, {
123
190
  ...innerProps,
124
- ref: mergeRefs(ref, field.ref),
191
+ ref: mergeRefs(ref, field.ref, inputRef),
125
192
  value: field.value,
126
193
  onChange: field.onChange,
127
194
  onBlur: field.onBlur,
128
195
  isDirty,
129
196
  isDisabled: field.disabled || props.isDisabled,
130
- error: props.error ?? error?.message
197
+ error: props.error ?? error?.message,
198
+ autoFocusOnMount: shouldFocus
131
199
  }, field.value === null || field.value === void 0 ? "empty" : "filled")
132
200
  });
133
201
  }
134
- return /* @__PURE__ */ jsx(NumberInputBase, { ...props }, props.value === null || props.value === void 0 ? "empty" : "filled");
202
+ return /* @__PURE__ */ jsx(NumberInputBase, {
203
+ ...props,
204
+ ref: mergeRefs(props.ref, inputRef),
205
+ autoFocusOnMount: shouldFocus
206
+ }, props.value === null || props.value === void 0 ? "empty" : "filled");
135
207
  };
136
208
  //#endregion
137
209
  export { NumberInput };
@@ -13,6 +13,7 @@ interface TextInputBaseProps extends FormFieldProps, InputVariantProps, Omit<Inp
13
13
  type?: AllowedHTMLInputTypeAttribute;
14
14
  todayIcon?: boolean;
15
15
  isDirty?: boolean;
16
+ autoFocusOnMount?: boolean;
16
17
  }
17
18
  export interface TextInputProps extends TextInputBaseProps {
18
19
  }
@@ -1,22 +1,24 @@
1
1
  import { UIStyle } from "../../../../config/uiStyle.context.js";
2
+ import { Loader } from "../../../status/Loader/Loader.js";
2
3
  import { UIConfig } from "../../../../config/uiConfig.context.js";
3
4
  import { inputBase } from "../../shared/input.cva.js";
4
5
  import { FormField } from "../../FormField/FormField.js";
6
+ import { StaticInput } from "../../shared/StaticInput.js";
5
7
  import { TooltipWrapper } from "../../shared/TooltipWrapper.js";
6
8
  import { InputContent } from "../shared/InputContent.js";
7
- import { jsx } from "react/jsx-runtime";
9
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
8
10
  import { clsx } from "clsx";
9
- import { useRef } from "react";
11
+ import { isValidElement, useEffect, useRef, useState } from "react";
10
12
  import { Input } from "react-aria-components";
11
13
  import { useFocusVisible, useTextField } from "react-aria";
12
14
  import { mergeRefs } from "@react-aria/utils";
13
- import { Controller } from "react-hook-form";
15
+ import { Controller, useWatch } from "react-hook-form";
14
16
  //#region src/components/inputs/Input/TextInput/TextInput.tsx
15
17
  var TextInputBase = (props) => {
16
18
  const ui = UIConfig.useConfig();
17
19
  const inputBaseCva = UIStyle.useCva("input.baseCva", inputBase);
18
20
  const inputRef = useRef(null);
19
- const { ref, inputClassName, label, tooltipText, helperText, isRequired, rightContent, isDisabled, isDirty, headerClassName, errorClassName, isHeaderHidden, error, className, unit, isLoading, action, leadingIcon, trailingIcon, value, onChange, onBlur, as = ui.input.as, hideLabel = ui.input.hideLabel, variant = ui.input.variant, size = ui.input.size, isClearable = ui.input.isClearable, ...rest } = props;
21
+ const { ref, inputClassName, label, tooltipText, helperText, isRequired, rightContent, isDisabled, isDirty, headerClassName, errorClassName, isHeaderHidden, error, className, unit, isLoading, action, leadingIcon, trailingIcon, value, onChange, onBlur, as = ui.input.as, hideLabel = ui.input.hideLabel, variant = ui.input.variant, size = ui.input.size, isClearable = ui.input.isClearable, autoFocusOnMount, ...rest } = props;
20
22
  const textFieldRef = useRef(null);
21
23
  const { isFocusVisible } = useFocusVisible();
22
24
  const { labelProps, inputProps } = useTextField({
@@ -29,6 +31,10 @@ var TextInputBase = (props) => {
29
31
  onChange,
30
32
  onBlur
31
33
  }, textFieldRef);
34
+ useEffect(() => {
35
+ if (!autoFocusOnMount || isDisabled) return;
36
+ requestAnimationFrame(() => inputRef.current?.focus());
37
+ }, [autoFocusOnMount, isDisabled]);
32
38
  const formFieldProps = {
33
39
  error,
34
40
  label,
@@ -106,7 +112,71 @@ var TextInputBase = (props) => {
106
112
  })
107
113
  });
108
114
  };
115
+ var renderIconVisual = (icon) => {
116
+ if (!icon) return null;
117
+ if (isValidElement(icon)) return icon;
118
+ return /* @__PURE__ */ jsx(icon, { className: "size-6 text-interactive-text-secondary-idle" });
119
+ };
109
120
  var TextInput = (props) => {
121
+ const ui = UIConfig.useConfig();
122
+ const [renderInput, setRenderInput] = useState(!ui.renderStaticInput);
123
+ const [shouldFocus, setShouldFocus] = useState(false);
124
+ const inputRef = useRef(null);
125
+ const watchedValue = "formControl" in props && props.formControl ? useWatch({
126
+ control: props.formControl.control,
127
+ name: props.formControl.name
128
+ }) : props.value;
129
+ let isFormControlDisabled = false;
130
+ if ("formControl" in props && props.formControl) isFormControlDisabled = !!props.formControl.control._options?.disabled;
131
+ const staticValue = watchedValue ?? props.value ?? props.defaultValue ?? "";
132
+ const inputType = props.type;
133
+ if (!renderInput && inputType === "password" && `${staticValue}`.length > 0) setRenderInput(true);
134
+ useEffect(() => {
135
+ if (!renderInput || !shouldFocus) return;
136
+ requestAnimationFrame(() => inputRef.current?.focus());
137
+ setShouldFocus(false);
138
+ }, [renderInput, shouldFocus]);
139
+ if (!renderInput) {
140
+ const as = props.as ?? ui.input.as;
141
+ const size = props.size ?? ui.input.size;
142
+ const variant = props.variant ?? ui.input.variant;
143
+ const hideLabel = props.hideLabel ?? ui.input.hideLabel;
144
+ const isHeaderHidden = props.isHeaderHidden || as === "inline" || as === "filter" || as === "floating";
145
+ let isDisabled = !!props.isDisabled;
146
+ if (isFormControlDisabled) isDisabled = true;
147
+ const maskedPasswordValue = inputType === "password" && `${staticValue}`.length > 0 ? "•".repeat(`${staticValue}`.length) : staticValue;
148
+ const displayValue = maskedPasswordValue == null ? "" : `${maskedPasswordValue}`;
149
+ const hasValue = displayValue !== "";
150
+ const trailingVisual = !!props.unit || !!props.isLoading || !!props.action && !props.isLoading || !!props.trailingIcon && !props.isLoading && !props.action ? /* @__PURE__ */ jsxs(Fragment, { children: [
151
+ props.unit && /* @__PURE__ */ jsx("span", {
152
+ className: "text-label-2 text-text-default-3",
153
+ children: props.unit
154
+ }),
155
+ props.isLoading && /* @__PURE__ */ jsx(Loader, {}),
156
+ !props.isLoading && props.action && renderIconVisual(props.action.icon),
157
+ !props.isLoading && !props.action && props.trailingIcon && renderIconVisual(props.trailingIcon)
158
+ ] }) : void 0;
159
+ return /* @__PURE__ */ jsx(StaticInput, {
160
+ ...props,
161
+ onInteract: (focus) => {
162
+ setShouldFocus(focus);
163
+ setRenderInput(true);
164
+ },
165
+ as,
166
+ size,
167
+ variant,
168
+ hideLabel,
169
+ isHeaderHidden,
170
+ isDisabled,
171
+ className: clsx("group w-full", as === "inline" && "h-full", props.className),
172
+ inputClassName: props.inputClassName,
173
+ placeholder: as === "floating" ? "" : props.placeholder,
174
+ displayValue,
175
+ isEmpty: !hasValue,
176
+ leadingVisual: renderIconVisual(props.leadingIcon),
177
+ trailingVisual
178
+ });
179
+ }
110
180
  if ("formControl" in props && props.formControl) {
111
181
  const { formControl, ref, ...innerProps } = props;
112
182
  return /* @__PURE__ */ jsx(Controller, {
@@ -114,17 +184,22 @@ var TextInput = (props) => {
114
184
  name: formControl.name,
115
185
  render: ({ field, fieldState: { error, isDirty } }) => /* @__PURE__ */ jsx(TextInputBase, {
116
186
  ...innerProps,
117
- ref: mergeRefs(ref, field.ref),
187
+ ref: mergeRefs(ref, field.ref, inputRef),
118
188
  value: field.value,
119
189
  onChange: field.onChange,
120
190
  onBlur: field.onBlur,
121
191
  isDirty,
122
192
  isDisabled: field.disabled || props.isDisabled,
123
- error: props.error ?? error?.message
193
+ error: props.error ?? error?.message,
194
+ autoFocusOnMount: shouldFocus
124
195
  })
125
196
  });
126
197
  }
127
- return /* @__PURE__ */ jsx(TextInputBase, { ...props });
198
+ return /* @__PURE__ */ jsx(TextInputBase, {
199
+ ...props,
200
+ ref: mergeRefs(props.ref, inputRef),
201
+ autoFocusOnMount: shouldFocus
202
+ });
128
203
  };
129
204
  //#endregion
130
205
  export { TextInput };
@@ -1,9 +1,13 @@
1
+ import { ArrowDropDownIcon } from "../../../../assets/icons/ArrowDropDown.js";
2
+ import { UIConfig } from "../../../../config/uiConfig.context.js";
3
+ import { StaticInput } from "../../shared/StaticInput.js";
1
4
  import { SelectBase } from "../shared/SelectBase.js";
2
5
  import { jsx } from "react/jsx-runtime";
6
+ import { isValidElement, useEffect, useRef, useState } from "react";
3
7
  import { mergeRefs } from "@react-aria/utils";
4
- import { Controller } from "react-hook-form";
8
+ import { Controller, useWatch } from "react-hook-form";
5
9
  //#region src/components/inputs/Selection/Autocomplete/Autocomplete.tsx
6
- var Autocomplete = (props) => {
10
+ var _Autocomplete = (props) => {
7
11
  if ("formControl" in props && props.formControl) {
8
12
  const { formControl, ref, ...innerProps } = props;
9
13
  return /* @__PURE__ */ jsx(Controller, {
@@ -26,5 +30,61 @@ var Autocomplete = (props) => {
26
30
  isSearchable: true
27
31
  });
28
32
  };
33
+ var Autocomplete = (props) => {
34
+ const ui = UIConfig.useConfig();
35
+ const [renderInput, setRenderInput] = useState(!ui.renderStaticInput);
36
+ const [shouldFocus, setShouldFocus] = useState(false);
37
+ const rootRef = useRef(null);
38
+ const currentValue = ("formControl" in props && props.formControl ? useWatch({
39
+ control: props.formControl.control,
40
+ name: props.formControl.name
41
+ }) : props.value) ?? props.value;
42
+ let isFormControlDisabled = false;
43
+ if ("formControl" in props && props.formControl) isFormControlDisabled = !!props.formControl.control._options?.disabled;
44
+ useEffect(() => {
45
+ if (!renderInput || !shouldFocus) return;
46
+ requestAnimationFrame(() => {
47
+ (rootRef.current?.querySelector("input, [data-type='select-trigger'], button, [tabindex]"))?.focus();
48
+ });
49
+ setShouldFocus(false);
50
+ }, [renderInput, shouldFocus]);
51
+ if (!renderInput) {
52
+ const getItemLabel = (id) => {
53
+ const item = props.items.find((innerItem) => innerItem.id === id);
54
+ if (!item) return "";
55
+ if (props.showSelectionContent && item.content) return isValidElement(item.content) ? item.label : item.content;
56
+ return item.label;
57
+ };
58
+ const mode = props.selectionMode ?? ui.select.selectionMode;
59
+ let displayValue = "";
60
+ if (mode === "multiple") {
61
+ if (Array.isArray(currentValue) && currentValue.length > 0) displayValue = currentValue.map((id) => getItemLabel(id)).filter(Boolean).join(", ");
62
+ } else if (currentValue != null) displayValue = getItemLabel(currentValue);
63
+ const as = props.as ?? ui.input.as;
64
+ let isDisabled = !!props.isDisabled;
65
+ if (isFormControlDisabled) isDisabled = true;
66
+ return /* @__PURE__ */ jsx(StaticInput, {
67
+ ...props,
68
+ onInteract: (focus) => {
69
+ setShouldFocus(focus);
70
+ setRenderInput(true);
71
+ },
72
+ as,
73
+ size: props.size ?? ui.input.size,
74
+ variant: props.variant ?? ui.input.variant,
75
+ isHeaderHidden: props.isHeaderHidden || as === "inline",
76
+ hideLabel: props.hideLabel ?? ui.input.hideLabel,
77
+ isDisabled,
78
+ placeholder: as === "floating" ? "" : props.placeholder,
79
+ displayValue,
80
+ isEmpty: !displayValue,
81
+ trailingVisual: /* @__PURE__ */ jsx(ArrowDropDownIcon, { className: "size-6 shrink-0 text-interactive-text-secondary-idle" })
82
+ });
83
+ }
84
+ return /* @__PURE__ */ jsx(_Autocomplete, {
85
+ ...props,
86
+ ref: mergeRefs(props.ref, rootRef)
87
+ });
88
+ };
29
89
  //#endregion
30
90
  export { Autocomplete };
@@ -1,9 +1,13 @@
1
+ import { ArrowDropDownIcon } from "../../../../assets/icons/ArrowDropDown.js";
2
+ import { UIConfig } from "../../../../config/uiConfig.context.js";
3
+ import { StaticInput } from "../../shared/StaticInput.js";
1
4
  import { SelectBase } from "../shared/SelectBase.js";
2
5
  import { jsx } from "react/jsx-runtime";
6
+ import { isValidElement, useEffect, useRef, useState } from "react";
3
7
  import { mergeRefs } from "@react-aria/utils";
4
- import { Controller } from "react-hook-form";
8
+ import { Controller, useWatch } from "react-hook-form";
5
9
  //#region src/components/inputs/Selection/Select/Select.tsx
6
- var Select = (props) => {
10
+ var _Select = (props) => {
7
11
  if ("formControl" in props && props.formControl) {
8
12
  const { formControl, ref, ...innerProps } = props;
9
13
  return /* @__PURE__ */ jsx(Controller, {
@@ -22,5 +26,62 @@ var Select = (props) => {
22
26
  }
23
27
  return /* @__PURE__ */ jsx(SelectBase, { ...props });
24
28
  };
29
+ var Select = (props) => {
30
+ const ui = UIConfig.useConfig();
31
+ const [renderInput, setRenderInput] = useState(!ui.renderStaticInput);
32
+ const [shouldFocus, setShouldFocus] = useState(false);
33
+ const rootRef = useRef(null);
34
+ const currentValue = ("formControl" in props && props.formControl ? useWatch({
35
+ control: props.formControl.control,
36
+ name: props.formControl.name
37
+ }) : props.value) ?? props.value;
38
+ let isFormControlDisabled = false;
39
+ if ("formControl" in props && props.formControl) isFormControlDisabled = !!props.formControl.control._options?.disabled;
40
+ if (!renderInput && props.selectionMode !== "multiple" && !!props.showSelectionContent && currentValue != null) setRenderInput(true);
41
+ useEffect(() => {
42
+ if (!renderInput || !shouldFocus) return;
43
+ requestAnimationFrame(() => {
44
+ (rootRef.current?.querySelector("[data-type='select-trigger'], input, button, [tabindex]"))?.focus();
45
+ });
46
+ setShouldFocus(false);
47
+ }, [renderInput, shouldFocus]);
48
+ if (!renderInput) {
49
+ const getItemLabel = (id) => {
50
+ const item = props.items.find((innerItem) => innerItem.id === id);
51
+ if (!item) return "";
52
+ if (props.showSelectionContent && item.content) return isValidElement(item.content) ? item.label : item.content;
53
+ return item.label;
54
+ };
55
+ const mode = props.selectionMode ?? ui.select.selectionMode;
56
+ let displayValue = "";
57
+ if (mode === "multiple") {
58
+ if (Array.isArray(currentValue) && currentValue.length > 0) displayValue = currentValue.map((id) => getItemLabel(id)).filter(Boolean).join(", ");
59
+ } else if (currentValue != null) displayValue = getItemLabel(currentValue);
60
+ const as = props.as ?? ui.input.as;
61
+ let isDisabled = !!props.isDisabled;
62
+ if (isFormControlDisabled) isDisabled = true;
63
+ return /* @__PURE__ */ jsx(StaticInput, {
64
+ ...props,
65
+ onInteract: (focus) => {
66
+ setShouldFocus(focus);
67
+ setRenderInput(true);
68
+ },
69
+ as,
70
+ size: props.size ?? ui.input.size,
71
+ variant: props.variant ?? ui.input.variant,
72
+ isHeaderHidden: props.isHeaderHidden || as === "inline",
73
+ hideLabel: props.hideLabel ?? ui.input.hideLabel,
74
+ isDisabled,
75
+ placeholder: as === "floating" ? "" : props.placeholder,
76
+ displayValue,
77
+ isEmpty: !displayValue,
78
+ trailingVisual: /* @__PURE__ */ jsx(ArrowDropDownIcon, { className: "size-6 shrink-0 text-interactive-text-secondary-idle" })
79
+ });
80
+ }
81
+ return /* @__PURE__ */ jsx(_Select, {
82
+ ...props,
83
+ ref: mergeRefs(props.ref, rootRef)
84
+ });
85
+ };
25
86
  //#endregion
26
87
  export { Select };
@@ -0,0 +1,18 @@
1
+ import { ReactNode } from 'react';
2
+ import { FormFieldProps } from '../FormField/FormField';
3
+ import { TextInputProps } from '../Input/TextInput/TextInput';
4
+ import { InputSizeProps, InputVariantProps } from './input.cva';
5
+ interface StaticInputProps extends FormFieldProps {
6
+ onInteract: (shouldFocus: boolean) => void;
7
+ as?: TextInputProps["as"];
8
+ size?: InputSizeProps["size"];
9
+ variant?: InputVariantProps["variant"];
10
+ placeholder?: ReactNode;
11
+ displayValue?: ReactNode;
12
+ isEmpty?: boolean;
13
+ leadingVisual?: ReactNode;
14
+ trailingVisual?: ReactNode;
15
+ inputClassName?: string;
16
+ }
17
+ export declare const StaticInput: ({ onInteract, as, size, variant, placeholder, displayValue, isEmpty, leadingVisual, trailingVisual, inputClassName, className, ...formFieldProps }: StaticInputProps) => import("react/jsx-runtime").JSX.Element;
18
+ export {};
@@ -0,0 +1,69 @@
1
+ import { UIStyle } from "../../../config/uiStyle.context.js";
2
+ import { FormFieldLabel } from "../FormField/FormFieldLabel.js";
3
+ import { inputBase, inputSize } from "./input.cva.js";
4
+ import { FormField } from "../FormField/FormField.js";
5
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
6
+ import { clsx } from "clsx";
7
+ //#region src/components/inputs/shared/StaticInput.tsx
8
+ var StaticInput = ({ onInteract, as, size, variant, placeholder, displayValue, isEmpty, leadingVisual, trailingVisual, inputClassName, className, ...formFieldProps }) => {
9
+ const inputBaseCva = UIStyle.useCva("input.baseCva", inputBase);
10
+ const inputSizeCva = UIStyle.useCva("input.sizeCva", inputSize);
11
+ let hasValue = !!displayValue;
12
+ if (isEmpty != null) hasValue = !isEmpty;
13
+ let staticDisplayContent = placeholder;
14
+ if (hasValue) staticDisplayContent = displayValue;
15
+ else if (as === "floating") staticDisplayContent = null;
16
+ const shouldRenderFloatingLineSpacer = as === "floating" && !hasValue;
17
+ return /* @__PURE__ */ jsx(FormField, {
18
+ ...formFieldProps,
19
+ as,
20
+ className,
21
+ onMouseEnter: () => onInteract(false),
22
+ tabIndex: as === "inline" ? -1 : void 0,
23
+ children: /* @__PURE__ */ jsxs("div", {
24
+ tabIndex: formFieldProps.isDisabled ? -1 : 0,
25
+ onFocus: () => onInteract(true),
26
+ onClick: () => onInteract(true),
27
+ onPointerDown: () => onInteract(true),
28
+ "data-is-empty": !hasValue || void 0,
29
+ "data-disabled": formFieldProps.isDisabled || void 0,
30
+ "data-is-disabled": formFieldProps.isDisabled || void 0,
31
+ "data-has-selection": hasValue || void 0,
32
+ "data-rac": "",
33
+ className: clsx("group/static-input group/input-content group/select-content relative flex min-w-input-width-min-width items-center justify-between gap-input-gap-input-text-to-elements", inputBaseCva({
34
+ as,
35
+ variant
36
+ }), inputClassName),
37
+ children: [/* @__PURE__ */ jsxs("div", {
38
+ className: clsx(inputSizeCva({
39
+ size,
40
+ as
41
+ }), "flex w-full items-center gap-input-gap-input-text-to-elements pr-0!"),
42
+ children: [leadingVisual && /* @__PURE__ */ jsx("div", {
43
+ className: "pointer-events-none shrink-0",
44
+ children: leadingVisual
45
+ }), /* @__PURE__ */ jsxs("div", {
46
+ className: clsx("flex w-full truncate", as === "floating" && "flex-col", as === "filter" && "gap-input-gap-input-text-to-elements"),
47
+ children: [as && ["filter", "floating"].includes(as) && /* @__PURE__ */ jsx(FormFieldLabel, {
48
+ as,
49
+ size,
50
+ label: formFieldProps.label,
51
+ isRequired: formFieldProps.isRequired,
52
+ isDisabled: formFieldProps.isDisabled
53
+ }), /* @__PURE__ */ jsxs("div", {
54
+ className: clsx("truncate", !hasValue && "text-text-default-3"),
55
+ children: [staticDisplayContent, shouldRenderFloatingLineSpacer && /* @__PURE__ */ jsx(Fragment, { children: "\xA0" })]
56
+ })]
57
+ })]
58
+ }), trailingVisual && /* @__PURE__ */ jsx("div", {
59
+ className: clsx(inputSizeCva({
60
+ size,
61
+ as
62
+ }), "pointer-events-none flex items-center gap-input-gap-trailing-elements py-0! pl-0!"),
63
+ children: trailingVisual
64
+ })]
65
+ })
66
+ });
67
+ };
68
+ //#endregion
69
+ export { StaticInput };
@@ -19,6 +19,7 @@ export declare namespace UIConfig {
19
19
  [P in keyof T]-?: T[P] extends object ? DeepRequired<T[P]> : Exclude<T[P], null | undefined>;
20
20
  };
21
21
  export interface Options {
22
+ renderStaticInput: boolean;
22
23
  input: Pick<TextInputProps, "variant" | "isClearable" | "hideLabel" | "as" | "size">;
23
24
  button: Pick<ButtonProps, "variant" | "size">;
24
25
  numberInput: Pick<NumberInputProps, "formatOptions">;
@@ -6,6 +6,7 @@ import { createContext, use } from "react";
6
6
  var UIConfig;
7
7
  (function(_UIConfig) {
8
8
  const DEFAULT_CONFIG = {
9
+ renderStaticInput: true,
9
10
  input: {
10
11
  variant: "outlined",
11
12
  as: "default",
@@ -1,5 +1,15 @@
1
1
  import { CalendarDate, CalendarDateTime, DateValue, ZonedDateTime } from '@internationalized/date';
2
2
  export declare namespace DateTimeUtils {
3
+ function getDatePlaceholder(locale?: string): string;
4
+ function getTimePlaceholder(locale?: string): string;
5
+ function getDateTimePlaceholder(locale?: string): string;
6
+ function getDateRangePlaceholder(locale?: string): string;
7
+ function formatCalendarDateLocalized(calendarDate: CalendarDate, locale?: string): string;
8
+ function formatTimeLocalized(timeValue: {
9
+ hour: number;
10
+ minute: number;
11
+ }, locale?: string): string;
12
+ function formatCalendarDateTimeLocalized(calendarDateTime: Pick<CalendarDateTime, "day" | "month" | "year" | "hour" | "minute">, locale?: string): string;
3
13
  function fromISOtoZonedDateTime(isoString: string): ZonedDateTime;
4
14
  function fromDateValueToISO(dateValue: DateValue): string;
5
15
  function fromLocalToZonedDateTime(date: Date): ZonedDateTime;
@@ -1,7 +1,88 @@
1
- import { CalendarDate, CalendarDateTime, fromDate, getLocalTimeZone, parseAbsolute, parseAbsoluteToLocal } from "@internationalized/date";
1
+ import { CalendarDate, CalendarDateTime, DateFormatter, fromDate, getLocalTimeZone, parseAbsolute, parseAbsoluteToLocal } from "@internationalized/date";
2
2
  //#region src/utils/date-time.utils.ts
3
3
  var DateTimeUtils;
4
4
  (function(_DateTimeUtils) {
5
+ const DATE_SAMPLE_DATE_UTC = new Date(Date.UTC(2e3, 10, 22));
6
+ const TIME_SAMPLE_DATE_UTC = new Date(Date.UTC(2e3, 10, 22, 13, 45));
7
+ const getDatePlaceholderFormatter = (locale) => new Intl.DateTimeFormat(locale, {
8
+ day: "2-digit",
9
+ month: "2-digit",
10
+ year: "numeric"
11
+ });
12
+ const getTimePlaceholderFormatter = (locale) => new Intl.DateTimeFormat(locale, {
13
+ hour: "2-digit",
14
+ minute: "2-digit",
15
+ hourCycle: "h23"
16
+ });
17
+ const getResolvedLocale = (locale) => {
18
+ if (locale) return locale;
19
+ return new Intl.DateTimeFormat().resolvedOptions().locale;
20
+ };
21
+ const getDateFormatter = (locale) => new DateFormatter(getResolvedLocale(locale), {
22
+ day: "2-digit",
23
+ month: "2-digit",
24
+ year: "numeric",
25
+ timeZone: "UTC"
26
+ });
27
+ const getTimeFormatter = (locale) => new DateFormatter(getResolvedLocale(locale), {
28
+ hour: "2-digit",
29
+ minute: "2-digit",
30
+ hourCycle: "h23",
31
+ timeZone: "UTC"
32
+ });
33
+ const getDateTimeFormatter = (locale) => new DateFormatter(getResolvedLocale(locale), {
34
+ day: "2-digit",
35
+ month: "2-digit",
36
+ year: "numeric",
37
+ hour: "2-digit",
38
+ minute: "2-digit",
39
+ hourCycle: "h23",
40
+ timeZone: "UTC"
41
+ });
42
+ const mapTypeToPlaceholder = (type) => {
43
+ switch (type) {
44
+ case "day": return "dd";
45
+ case "month": return "mm";
46
+ case "year": return "yyyy";
47
+ case "hour": return "hh";
48
+ case "minute": return "mm";
49
+ default: return null;
50
+ }
51
+ };
52
+ const formatPlaceholder = (formatter, sampleDate) => {
53
+ return formatter.formatToParts(sampleDate).map((part) => mapTypeToPlaceholder(part.type) ?? part.value).join("");
54
+ };
55
+ function getDatePlaceholder(locale) {
56
+ return formatPlaceholder(getDatePlaceholderFormatter(locale), DATE_SAMPLE_DATE_UTC);
57
+ }
58
+ _DateTimeUtils.getDatePlaceholder = getDatePlaceholder;
59
+ function getTimePlaceholder(locale) {
60
+ return formatPlaceholder(getTimePlaceholderFormatter(locale), TIME_SAMPLE_DATE_UTC);
61
+ }
62
+ _DateTimeUtils.getTimePlaceholder = getTimePlaceholder;
63
+ function getDateTimePlaceholder(locale) {
64
+ return `${getDatePlaceholder(locale)}, ${getTimePlaceholder(locale)}`;
65
+ }
66
+ _DateTimeUtils.getDateTimePlaceholder = getDateTimePlaceholder;
67
+ function getDateRangePlaceholder(locale) {
68
+ const datePlaceholder = getDatePlaceholder(locale);
69
+ return `${datePlaceholder} – ${datePlaceholder}`;
70
+ }
71
+ _DateTimeUtils.getDateRangePlaceholder = getDateRangePlaceholder;
72
+ function formatCalendarDateLocalized(calendarDate, locale) {
73
+ return getDateFormatter(locale).format(calendarDate.toDate("UTC"));
74
+ }
75
+ _DateTimeUtils.formatCalendarDateLocalized = formatCalendarDateLocalized;
76
+ function formatTimeLocalized(timeValue, locale) {
77
+ const dateTime = new CalendarDateTime(2e3, 11, 22, timeValue.hour, timeValue.minute);
78
+ return getTimeFormatter(locale).format(dateTime.toDate("UTC"));
79
+ }
80
+ _DateTimeUtils.formatTimeLocalized = formatTimeLocalized;
81
+ function formatCalendarDateTimeLocalized(calendarDateTime, locale) {
82
+ const normalizedValue = new CalendarDateTime(calendarDateTime.year, calendarDateTime.month, calendarDateTime.day, calendarDateTime.hour, calendarDateTime.minute);
83
+ return getDateTimeFormatter(locale).format(normalizedValue.toDate("UTC"));
84
+ }
85
+ _DateTimeUtils.formatCalendarDateTimeLocalized = formatCalendarDateTimeLocalized;
5
86
  function fromISOtoZonedDateTime(isoString) {
6
87
  return parseAbsoluteToLocal(isoString);
7
88
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@povio/ui",
3
- "version": "2.2.7",
3
+ "version": "2.2.8-rc",
4
4
  "type": "module",
5
5
  "module": "dist/index.js",
6
6
  "types": "dist/index.d.ts",