@rebasepro/ui 0.17.3 → 0.18.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.
Files changed (127) hide show
  1. package/README.md +4 -0
  2. package/dist/components/MultiSelect.d.ts +10 -0
  3. package/dist/index.es.js +2 -1
  4. package/dist/index.es.js.map +1 -1
  5. package/package.json +32 -23
  6. package/src/components/Alert.tsx +0 -118
  7. package/src/components/Autocomplete.tsx +0 -93
  8. package/src/components/Avatar.tsx +0 -73
  9. package/src/components/Badge.tsx +0 -44
  10. package/src/components/BooleanSwitch.tsx +0 -116
  11. package/src/components/BooleanSwitchWithLabel.tsx +0 -120
  12. package/src/components/Button.tsx +0 -110
  13. package/src/components/Card.tsx +0 -43
  14. package/src/components/CenteredView.tsx +0 -35
  15. package/src/components/Checkbox.tsx +0 -129
  16. package/src/components/Chip.tsx +0 -131
  17. package/src/components/CircularProgress.tsx +0 -53
  18. package/src/components/CircularProgressCenter.tsx +0 -27
  19. package/src/components/Collapse.tsx +0 -66
  20. package/src/components/ColorPicker.tsx +0 -140
  21. package/src/components/Container.tsx +0 -51
  22. package/src/components/DateTimeField.tsx +0 -363
  23. package/src/components/DebouncedTextField.tsx +0 -76
  24. package/src/components/Dialog.tsx +0 -159
  25. package/src/components/DialogActions.tsx +0 -29
  26. package/src/components/DialogContent.tsx +0 -27
  27. package/src/components/DialogTitle.tsx +0 -41
  28. package/src/components/ErrorBoundary.tsx +0 -236
  29. package/src/components/ExpandablePanel.tsx +0 -130
  30. package/src/components/FileUpload.tsx +0 -103
  31. package/src/components/FilterChip.tsx +0 -89
  32. package/src/components/IconButton.tsx +0 -101
  33. package/src/components/InfoLabel.tsx +0 -23
  34. package/src/components/InputLabel.tsx +0 -40
  35. package/src/components/Label.tsx +0 -32
  36. package/src/components/LoadingButton.tsx +0 -32
  37. package/src/components/Markdown.tsx +0 -55
  38. package/src/components/Menu.tsx +0 -114
  39. package/src/components/Menubar.tsx +0 -355
  40. package/src/components/MultiSelect.tsx +0 -433
  41. package/src/components/Paper.tsx +0 -23
  42. package/src/components/Popover.tsx +0 -162
  43. package/src/components/RadioGroup.tsx +0 -74
  44. package/src/components/ResizablePanels.tsx +0 -189
  45. package/src/components/SearchBar.tsx +0 -136
  46. package/src/components/Select.tsx +0 -326
  47. package/src/components/Separator.tsx +0 -28
  48. package/src/components/Sheet.tsx +0 -135
  49. package/src/components/Skeleton.tsx +0 -54
  50. package/src/components/Slider.tsx +0 -110
  51. package/src/components/Table.tsx +0 -178
  52. package/src/components/Tabs.tsx +0 -185
  53. package/src/components/TextField.tsx +0 -270
  54. package/src/components/TextareaAutosize.tsx +0 -368
  55. package/src/components/ToggleButtonGroup.tsx +0 -69
  56. package/src/components/Tooltip.tsx +0 -153
  57. package/src/components/Typography.tsx +0 -132
  58. package/src/components/VirtualTable/VirtualTable.performance.test.tsx +0 -90
  59. package/src/components/VirtualTable/VirtualTable.tsx +0 -700
  60. package/src/components/VirtualTable/VirtualTableCell.tsx +0 -59
  61. package/src/components/VirtualTable/VirtualTableHeader.tsx +0 -327
  62. package/src/components/VirtualTable/VirtualTableHeaderRow.tsx +0 -250
  63. package/src/components/VirtualTable/VirtualTableProps.tsx +0 -339
  64. package/src/components/VirtualTable/VirtualTableRow.tsx +0 -51
  65. package/src/components/VirtualTable/fields/VirtualTableDateField.tsx +0 -44
  66. package/src/components/VirtualTable/fields/VirtualTableInput.tsx +0 -89
  67. package/src/components/VirtualTable/fields/VirtualTableNumberInput.tsx +0 -71
  68. package/src/components/VirtualTable/fields/VirtualTableSelect.tsx +0 -107
  69. package/src/components/VirtualTable/fields/VirtualTableSwitch.tsx +0 -28
  70. package/src/components/VirtualTable/index.tsx +0 -10
  71. package/src/components/VirtualTable/selection/SelectionContext.tsx +0 -36
  72. package/src/components/VirtualTable/selection/SelectionStore.ts +0 -54
  73. package/src/components/VirtualTable/types.tsx +0 -47
  74. package/src/components/common/SelectInputLabel.tsx +0 -23
  75. package/src/components/index.tsx +0 -62
  76. package/src/hooks/PortalContainerContext.tsx +0 -50
  77. package/src/hooks/index.ts +0 -7
  78. package/src/hooks/useDebounceCallback.tsx +0 -21
  79. package/src/hooks/useDebounceValue.tsx +0 -20
  80. package/src/hooks/useDebouncedCallback.ts +0 -39
  81. package/src/hooks/useInjectStyles.tsx +0 -31
  82. package/src/hooks/useOutsideAlerter.tsx +0 -46
  83. package/src/icons/GitHubIcon.tsx +0 -21
  84. package/src/icons/HandleIcon.tsx +0 -23
  85. package/src/icons/Icon.tsx +0 -33
  86. package/src/icons/LucideIconByName.tsx +0 -117
  87. package/src/icons/cool_icon_keys.ts +0 -12
  88. package/src/icons/icon_keys.ts +0 -1936
  89. package/src/icons/index.ts +0 -179
  90. package/src/index.css +0 -169
  91. package/src/index.ts +0 -47
  92. package/src/scripts/fixIconSizes.cjs +0 -92
  93. package/src/scripts/fixIconSizesToConst.cjs +0 -106
  94. package/src/scripts/generateIconKeys.ts +0 -41
  95. package/src/scripts/generateIcons.ts +0 -4
  96. package/src/scripts/saveIconFiles.ts +0 -89
  97. package/src/styles.ts +0 -141
  98. package/src/theme.css +0 -168
  99. package/src/util/chip_colors.ts +0 -242
  100. package/src/util/cls.ts +0 -8
  101. package/src/util/debounce.ts +0 -28
  102. package/src/util/hash.ts +0 -11
  103. package/src/util/index.ts +0 -4
  104. package/src/util/key_to_icon_component.ts +0 -11
  105. package/src/util/lazy_chunk.ts +0 -101
  106. package/src/views/CardView.tsx +0 -281
  107. package/src/views/CollectionView/CollectionCardView.tsx +0 -265
  108. package/src/views/CollectionView/CollectionKanbanView.tsx +0 -246
  109. package/src/views/CollectionView/CollectionListView.tsx +0 -240
  110. package/src/views/CollectionView/CollectionTableView.tsx +0 -229
  111. package/src/views/CollectionView/CollectionView.tsx +0 -450
  112. package/src/views/CollectionView/CollectionViewToolbar.tsx +0 -242
  113. package/src/views/CollectionView/CollectionViewTypes.ts +0 -181
  114. package/src/views/CollectionView/DefaultCellRenderer.tsx +0 -404
  115. package/src/views/CollectionView/index.ts +0 -39
  116. package/src/views/CollectionView/resolveTitleProperty.ts +0 -52
  117. package/src/views/CollectionView/utils.ts +0 -14
  118. package/src/views/Kanban/Board.tsx +0 -384
  119. package/src/views/Kanban/BoardColumn.tsx +0 -138
  120. package/src/views/Kanban/BoardColumnTitle.tsx +0 -47
  121. package/src/views/Kanban/BoardSortableList.tsx +0 -182
  122. package/src/views/Kanban/board_types.ts +0 -80
  123. package/src/views/Kanban/index.tsx +0 -5
  124. package/src/views/Kanban/placement.ts +0 -62
  125. package/src/views/KanbanView.tsx +0 -32
  126. package/src/views/ListView.tsx +0 -296
  127. package/src/views/index.ts +0 -5
@@ -1,140 +0,0 @@
1
-
2
- import React from "react";
3
- import { CHIP_COLORS, cls } from "../util";
4
- import { ChipColorKey, ChipColorScheme } from "./Chip";
5
- import type { ChipTone } from "../util/chip_colors";
6
- import { CheckIcon } from "lucide-react";
7
- import { Tooltip } from "./Tooltip";
8
-
9
- export interface ColorPickerProps {
10
- /**
11
- * Currently selected color key
12
- */
13
- value?: ChipColorKey;
14
- /**
15
- * Callback when color selection changes. Passes undefined when "Auto" is selected.
16
- */
17
- onChange: (colorKey: ChipColorKey | undefined) => void;
18
- /**
19
- * Size of the color swatches
20
- */
21
- size?: "small" | "medium";
22
- /**
23
- * Whether to show the "Auto" option that clears the selection
24
- */
25
- allowClear?: boolean;
26
- /**
27
- * Whether the picker is disabled
28
- */
29
- disabled?: boolean;
30
- }
31
-
32
- // Hues across, tones down — the arrangement the palette is built in.
33
- const BASE_COLORS = ["blue", "indigo", "violet", "purple", "fuchsia", "pink", "rose", "red", "orange", "yellow", "green", "emerald", "teal", "cyan", "gray"] as const;
34
-
35
- // Lightest first, matching how the bare hue name reads.
36
- const TONES: ChipTone[] = ["Lighter", "Light", "Dark", "Darker"];
37
-
38
- // Helper to get readable name from color key
39
- function getColorDisplayName(hue: string, tone: ChipTone): string {
40
- const hueName = `${hue.charAt(0).toUpperCase()}${hue.slice(1)}`;
41
- return tone === "Lighter" ? hueName : `${hueName} ${tone.toLowerCase()}`;
42
- }
43
-
44
- /**
45
- * A color picker component that displays a grid of predefined CHIP_COLORS.
46
- * Used for selecting colors for enum values, tags, and other chip-based UI elements.
47
- *
48
- * @group Form components
49
- */
50
- export function ColorPicker({
51
- value,
52
- onChange,
53
- size = "medium",
54
- allowClear = true,
55
- disabled = false
56
- }: ColorPickerProps) {
57
-
58
- const swatchSize = size === "small" ? "w-5 h-5" : "w-6 h-6";
59
- const checkSize = size === "small" ? 12 : 14;
60
-
61
- return (
62
- <div className="flex flex-col gap-2">
63
- {allowClear && (
64
- <button
65
- type="button"
66
- disabled={disabled}
67
- onClick={() => onChange(undefined)}
68
- className={cls(
69
- "flex items-center gap-2 px-2 py-1 rounded-lg text-sm transition-colors duration-150",
70
- "hover:bg-surface-accent-100 dark:hover:bg-surface-accent-800 focus:outline-none focus-visible:ring-2 focus-visible:ring-primary",
71
- disabled && "opacity-50 cursor-not-allowed",
72
- !value && "bg-surface-accent-100 dark:bg-surface-accent-800 font-medium"
73
- )}
74
- >
75
- <div className={cls(
76
- swatchSize,
77
- "rounded-full border-2 border-dashed border-surface-accent-400 dark:border-surface-accent-600",
78
- "flex items-center justify-center"
79
- )}>
80
- {!value && <CheckIcon size={checkSize}/>}
81
- </div>
82
- <span className="text-surface-accent-700 dark:text-surface-accent-300">
83
- Auto (based on ID)
84
- </span>
85
- </button>
86
- )}
87
-
88
- {/* One row per tone. Wrapping rather than a fixed 15-column grid:
89
- the picker also opens inside narrow property panels, where
90
- fifteen tracks would shrink each swatch to a few pixels. */}
91
- {TONES.map((tone) => (
92
- <div key={tone} className="flex flex-wrap gap-1.5">
93
- {BASE_COLORS.map((base) => {
94
- // The bare hue and its `Lighter` tone are the same
95
- // scheme; the picker offers the short name so configs
96
- // stay readable.
97
- const colorKey = (tone === "Lighter" ? base : `${base}${tone}`) as ChipColorKey;
98
- const colorScheme = CHIP_COLORS[colorKey] as ChipColorScheme;
99
- const isSelected = value === colorKey;
100
- const displayName = getColorDisplayName(base, tone);
101
-
102
- return (
103
- <Tooltip
104
- key={colorKey}
105
- title={displayName}
106
- delayDuration={300}
107
- >
108
- <button
109
- type="button"
110
- disabled={disabled}
111
- onClick={() => onChange(colorKey)}
112
- className={cls(
113
- swatchSize,
114
- "rounded-full transition-all flex items-center justify-center",
115
- "hover:scale-110 hover:shadow-md",
116
- "focus:outline-none focus-visible:ring-2 focus-visible:ring-primary focus-visible:ring-offset-1",
117
- disabled && "opacity-50 cursor-not-allowed hover:scale-100",
118
- isSelected && "ring-2 ring-primary ring-offset-1"
119
- )}
120
- style={{
121
- backgroundColor: colorScheme.color
122
- }}
123
- aria-label={displayName}
124
- aria-pressed={isSelected}
125
- >
126
- {isSelected && (
127
- <CheckIcon
128
- size={checkSize}
129
- style={{ color: colorScheme.text }}
130
- />
131
- )}
132
- </button>
133
- </Tooltip>
134
- );
135
- })}
136
- </div>
137
- ))}
138
- </div>
139
- );
140
- }
@@ -1,51 +0,0 @@
1
- "use client";
2
- import React, { ForwardRefRenderFunction } from "react";
3
- import { cls } from "../util";
4
-
5
- export type ContainerProps = {
6
- children: React.ReactNode;
7
- style?: React.CSSProperties;
8
- className?: string;
9
- maxWidth?: "xs" | "sm" | "md" | "lg" | "xl" | "2xl" | "3xl" | "4xl" | "5xl" | "6xl" | "7xl";
10
- }
11
-
12
- const containerMaxWidths = {
13
- xs: "max-w-xs",
14
- sm: "max-w-sm",
15
- md: "max-w-md",
16
- lg: "max-w-lg",
17
- xl: "max-w-xl",
18
- "2xl": "max-w-2xl",
19
- "3xl": "max-w-3xl",
20
- "4xl": "max-w-4xl",
21
- "5xl": "max-w-5xl",
22
- "6xl": "max-w-6xl",
23
- "7xl": "max-w-7xl"
24
- }
25
-
26
- const ContainerInner: ForwardRefRenderFunction<HTMLDivElement, ContainerProps> = (
27
- {
28
- children,
29
- className,
30
- style,
31
- maxWidth = "7xl"
32
- },
33
- ref
34
- ) => {
35
-
36
- const classForMaxWidth = maxWidth ? containerMaxWidths[maxWidth] : "";
37
-
38
- return (
39
- <div
40
- ref={ref}
41
- className={cls("mx-auto px-3 md:px-4 lg:px-6",
42
- classForMaxWidth,
43
- className)}
44
- style={style}>
45
- {children}
46
- </div>
47
- );
48
- }
49
-
50
- export const Container = React.forwardRef(ContainerInner);
51
-
@@ -1,363 +0,0 @@
1
-
2
- "use client";
3
-
4
- import React, { useId, useRef, useState } from "react";
5
- import { CalendarIcon, XIcon, AlertCircleIcon } from "lucide-react";
6
- import { iconSize } from "../icons/Icon";
7
- import { IconButton } from "./IconButton";
8
- import { fieldBackgroundDisabledMixin, fieldBackgroundHoverMixin, fieldBackgroundMixin } from "../styles";
9
- import { InputLabel } from "./InputLabel";
10
- import { Typography } from "./Typography";
11
- import { cls } from "../util";
12
- import { useInjectStyles } from "../hooks";
13
-
14
- export type DateTimeFieldProps = {
15
- value?: Date | null;
16
- onChange?: (date: Date | null) => void;
17
- mode?: "date" | "date_time";
18
- disabled?: boolean;
19
- clearable?: boolean;
20
- error?: boolean;
21
- size?: "smallest" | "small" | "medium" | "large";
22
- label?: React.ReactNode;
23
- className?: string;
24
- style?: React.CSSProperties;
25
- inputClassName?: string;
26
- invisible?: boolean;
27
- locale?: string;
28
- /**
29
- * IANA timezone string (e.g., "America/New_York", "Europe/London").
30
- * Used to display and input dates in the specified timezone.
31
- * The value passed to onChange will always be in UTC.
32
- * If not provided, uses the user's local timezone.
33
- */
34
- timezone?: string;
35
- /**
36
- * Names the control when there is no `label` — e.g. when the surrounding
37
- * form draws the label itself and passes `label={undefined}`.
38
- */
39
- "aria-label"?: string;
40
- };
41
-
42
- export const DateTimeField: React.FC<DateTimeFieldProps> = ({
43
- value,
44
- label,
45
- onChange,
46
- disabled,
47
- clearable,
48
- mode = "date",
49
- error,
50
- size = "large",
51
- className,
52
- style,
53
- inputClassName,
54
- invisible,
55
- timezone,
56
- "aria-label": ariaLabel
57
- }) => {
58
- const inputRef = useRef<HTMLInputElement>(null);
59
- const [focused, setFocused] = useState(false);
60
- // The label was decorative: an InputLabel with no `htmlFor` beside an input
61
- // with no `id`, so a date field announced itself by its value even when it
62
- // visibly had a label. Wired the way TextField already does it.
63
- const inputId = useId();
64
- const labelId = `${inputId}-label`;
65
- const [internalValue, setInternalValue] = useState<string>("");
66
- const [isTyping, setIsTyping] = useState(false);
67
- const invalidValue = value !== undefined && value !== null && (!(value instanceof Date) || isNaN(value.getTime()));
68
-
69
- // The calendar/clear adornments are IconButtons. Left at their default
70
- // (`medium`, 40px) they floored the whole field at 40px, so smallest/small
71
- // rendered identically to medium. Keep them a step below the field.
72
- const adornmentSize = size === "large" ? "medium" : size === "medium" ? "small" : "smallest";
73
-
74
- useInjectStyles("DateTimeField", inputStyles);
75
-
76
- const handleClear = (e: React.MouseEvent) => {
77
- e.preventDefault();
78
- setInternalValue("");
79
- setIsTyping(false);
80
- onChange?.(null);
81
- };
82
-
83
- // Convert UTC Date to display string in the specified timezone
84
- const valueAsInputValue = (
85
- dateValue: Date | null,
86
- mode: "date" | "date_time"
87
- ) => {
88
- if (!dateValue || !(dateValue instanceof Date) || isNaN(dateValue.getTime())) {
89
- return "";
90
- }
91
- const pad = (n: number) => n.toString().padStart(2, "0");
92
-
93
- // Use Intl.DateTimeFormat to get date parts in the target timezone
94
- const options: Intl.DateTimeFormatOptions = {
95
- year: "numeric",
96
- month: "2-digit",
97
- day: "2-digit",
98
- hour: "2-digit",
99
- minute: "2-digit",
100
- hour12: false,
101
- timeZone: timezone // undefined = local timezone
102
- };
103
-
104
- const formatter = new Intl.DateTimeFormat("en-CA", options);
105
- const parts = formatter.formatToParts(dateValue);
106
-
107
- const getPart = (type: string) => parts.find(p => p.type === type)?.value ?? "";
108
-
109
- const year = getPart("year");
110
- const month = getPart("month");
111
- const day = getPart("day");
112
-
113
- if (mode === "date") {
114
- return `${year}-${month}-${day}`;
115
- } else {
116
- const hours = getPart("hour");
117
- const minutes = getPart("minute");
118
- return `${year}-${month}-${day}T${hours}:${minutes}`;
119
- }
120
- };
121
-
122
- // Get the UTC offset for a specific date in the target timezone (in minutes)
123
- const getTimezoneOffsetMinutes = (date: Date, tz?: string): number => {
124
- if (!tz) {
125
- // Local timezone: use built-in getTimezoneOffset (returns offset in minutes, inverted sign)
126
- return -date.getTimezoneOffset();
127
- }
128
- // For named timezones, calculate the offset by comparing formatted times
129
- const utcFormatter = new Intl.DateTimeFormat("en-CA", {
130
- year: "numeric",
131
- month: "2-digit",
132
- day: "2-digit",
133
- hour: "2-digit",
134
- minute: "2-digit",
135
- hour12: false,
136
- timeZone: "UTC"
137
- });
138
- const tzFormatter = new Intl.DateTimeFormat("en-CA", {
139
- year: "numeric",
140
- month: "2-digit",
141
- day: "2-digit",
142
- hour: "2-digit",
143
- minute: "2-digit",
144
- hour12: false,
145
- timeZone: tz
146
- });
147
-
148
- const utcParts = utcFormatter.formatToParts(date);
149
- const tzParts = tzFormatter.formatToParts(date);
150
-
151
- const getPart = (parts: Intl.DateTimeFormatPart[], type: string) =>
152
- parseInt(parts.find(p => p.type === type)?.value ?? "0", 10);
153
-
154
- const utcDate = new Date(Date.UTC(
155
- getPart(utcParts, "year"),
156
- getPart(utcParts, "month") - 1,
157
- getPart(utcParts, "day"),
158
- getPart(utcParts, "hour"),
159
- getPart(utcParts, "minute")
160
- ));
161
-
162
- const tzDate = new Date(Date.UTC(
163
- getPart(tzParts, "year"),
164
- getPart(tzParts, "month") - 1,
165
- getPart(tzParts, "day"),
166
- getPart(tzParts, "hour"),
167
- getPart(tzParts, "minute")
168
- ));
169
-
170
- return (tzDate.getTime() - utcDate.getTime()) / 60000;
171
- };
172
-
173
- // Handle input value change - convert from display timezone to UTC
174
- const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
175
- const inputValue = e.target.value;
176
- setInternalValue(inputValue);
177
- setIsTyping(true);
178
-
179
- if (!inputValue) {
180
- onChange?.(null);
181
- return;
182
- }
183
-
184
- try {
185
- let year: number, month: number, day: number, hours = 0, minutes = 0;
186
-
187
- if (mode === "date") {
188
- // Parse date-only input: "YYYY-MM-DD"
189
- [year, month, day] = inputValue.split("-").map(Number);
190
- } else {
191
- // Parse datetime-local input: "YYYY-MM-DDTHH:MM"
192
- const [datePart, timePart] = inputValue.split("T");
193
- [year, month, day] = datePart.split("-").map(Number);
194
- [hours, minutes] = timePart.split(":").map(Number);
195
- }
196
-
197
- let resultDate: Date;
198
-
199
- if (!timezone) {
200
- // No timezone specified: interpret input as local time (backward compatible)
201
- resultDate = new Date(year, month - 1, day, hours, minutes);
202
- } else {
203
- // Timezone specified: interpret input as that timezone and convert to UTC
204
- // We need to find the UTC equivalent of the entered time in the target timezone
205
-
206
- // Create a reference UTC date to calculate the offset for this moment
207
- const refUtcDate = new Date(Date.UTC(year, month - 1, day, hours, minutes));
208
- const offsetMinutes = getTimezoneOffsetMinutes(refUtcDate, timezone);
209
-
210
- // Convert from target timezone to UTC:
211
- // If user entered 00:00 in Mexico (UTC-6, offset=-360), we subtract the offset
212
- // Date.UTC gives us 00:00 UTC, subtracting -360 minutes (= adding 360 min) gives 06:00 UTC
213
- resultDate = new Date(Date.UTC(year, month - 1, day, hours, minutes) - offsetMinutes * 60000);
214
- }
215
-
216
- if (isNaN(resultDate.getTime())) {
217
- throw new Error("Invalid date");
218
- }
219
-
220
- onChange?.(resultDate);
221
- } catch (e) {
222
- // Don't call onChange with null while typing
223
- return;
224
- }
225
- };
226
-
227
- const handleFocus = () => {
228
- setFocused(true);
229
- setIsTyping(true);
230
- setInternalValue(valueAsInputValue(value ?? null, mode));
231
- };
232
-
233
- const handleBlur = () => {
234
- setFocused(false);
235
- setIsTyping(false);
236
- };
237
-
238
- return (
239
- <>
240
- {/* Inject the styles to hide the calendar icon */}
241
- <style>{inputStyles}</style>
242
- <div
243
- className={cls(
244
- "rounded-lg relative max-w-full",
245
- !invisible && fieldBackgroundMixin,
246
- disabled ? fieldBackgroundDisabledMixin : fieldBackgroundHoverMixin,
247
- {
248
- "min-h-[28px]": size === "smallest",
249
- "min-h-[32px]": size === "small",
250
- "min-h-[40px]": size === "medium",
251
- "min-h-[48px]": size === "large"
252
- },
253
- className
254
- )}
255
- style={style}
256
- onClick={() => {
257
- if (!disabled) {
258
- inputRef.current?.focus();
259
- }
260
- }}
261
- >
262
- {label && (
263
- <InputLabel
264
- id={labelId}
265
- htmlFor={inputId}
266
- className={cls(
267
- "absolute top-1 pointer-events-none",
268
- !error
269
- ? focused
270
- ? "text-primary"
271
- : "text-text-secondary dark:text-text-secondary-dark"
272
- : "text-red-600 dark:text-red-500",
273
- disabled ? "opacity-50" : ""
274
- )}
275
- shrink={true}
276
- // shrink={hasValue || focused}
277
- >
278
- {label}
279
- </InputLabel>
280
- )}
281
-
282
- <input
283
- ref={inputRef}
284
- id={inputId}
285
- aria-labelledby={label ? labelId : undefined}
286
- aria-label={label ? undefined : ariaLabel}
287
- type={mode === "date_time" ? "datetime-local" : "date"}
288
- value={isTyping ? internalValue : valueAsInputValue(value ?? null, mode)}
289
- onChange={handleInputChange}
290
- onFocus={handleFocus}
291
- onBlur={handleBlur}
292
- disabled={disabled}
293
- className={cls(
294
- "w-full outline-hidden bg-transparent leading-normal text-base px-3",
295
- clearable ? "pr-14" : "pr-12",
296
- "rounded-lg",
297
- {
298
- "min-h-[28px]": size === "smallest",
299
- "min-h-[32px]": size === "small",
300
- "min-h-[40px]": size === "medium",
301
- "min-h-[48px]": size === "large"
302
- },
303
- // A flat py-2 (16px) plus the text-base line box floored
304
- // the field at 40px, so smallest/small/medium all
305
- // rendered the same height. Scale it with `size`.
306
- label
307
- ? "pt-8 pb-2"
308
- : { smallest: "py-0.5", small: "py-1", medium: "py-2", large: "py-3" }[size],
309
- inputClassName,
310
- disabled &&
311
- "border border-transparent outline-hidden opacity-50 dark:opacity-50 text-surface-accent-600 dark:text-surface-accent-500"
312
- )}
313
- />
314
- <IconButton
315
- size={adornmentSize}
316
- onClick={(e) => {
317
- e.stopPropagation();
318
- inputRef.current?.showPicker();
319
- }}
320
- className="absolute right-3 top-1/2 transform -translate-y-1/2 text-surface-accent-500!"
321
- >
322
- <CalendarIcon/>
323
- </IconButton>
324
- {clearable && value && (
325
- <IconButton
326
- size={adornmentSize}
327
- onClick={handleClear}
328
- className="absolute right-14 top-1/2 transform -translate-y-1/2 text-surface-accent-400 "
329
- >
330
- <XIcon/>
331
- </IconButton>
332
- )}
333
- </div>
334
- {invalidValue && (
335
- <div className="flex items-center m-2">
336
- <AlertCircleIcon size={iconSize.small} color={"error"}/>
337
- <div className="pl-2">
338
- <Typography variant={"body2"}>
339
- Invalid date value for this field
340
- </Typography>
341
- <Typography variant={"body2"}>
342
- {`The provided value is: ${JSON.stringify(value)}`}
343
- </Typography>
344
- </div>
345
- </div>
346
- )}
347
- </>
348
- );
349
- };
350
-
351
- const inputStyles = `
352
- /* Hide the default calendar icon in Chrome, Safari, Edge, Opera */
353
- input[type="date"]::-webkit-calendar-picker-indicator,
354
- input[type="datetime-local"]::-webkit-calendar-picker-indicator {
355
- display: none;
356
- -webkit-appearance: none;
357
- }
358
- /* Hide default calendar icon in Firefox */
359
- input[type="date"],
360
- input[type="datetime-local"] {
361
- -moz-appearance:textfield;
362
- }
363
- `;
@@ -1,76 +0,0 @@
1
- "use client";
2
- import React, { useCallback, useEffect, useRef, useState } from "react";
3
- import { TextField, TextFieldProps } from "./TextField";
4
-
5
- type TextFieldChangeEvent = React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>;
6
-
7
- export function DebouncedTextField<T extends string | number>(props: TextFieldProps<T>) {
8
-
9
- const [internalValue, setInternalValue] = useState<T | string>(props.value ?? "");
10
- const lastSentValueRef = useRef<T | string>(props.value ?? "");
11
- const timerRef = useRef<ReturnType<typeof setTimeout> | undefined>(undefined);
12
-
13
- // Sync state with props.value when it changes externally
14
- useEffect(() => {
15
- const externalValue = props.value ?? "";
16
- if (externalValue !== lastSentValueRef.current) {
17
- setInternalValue(externalValue);
18
- lastSentValueRef.current = externalValue;
19
- }
20
- }, [props.value]);
21
-
22
- // Cleanup timer on unmount
23
- useEffect(() => {
24
- return () => {
25
- if (timerRef.current) clearTimeout(timerRef.current);
26
- };
27
- }, []);
28
-
29
- const flushChange = useCallback((value: T | string, event?: TextFieldChangeEvent) => {
30
- if (timerRef.current) {
31
- clearTimeout(timerRef.current);
32
- timerRef.current = undefined;
33
- }
34
- if (value !== props.value && props.onChange) {
35
- lastSentValueRef.current = value;
36
- const e = {
37
- ...event,
38
- target: {
39
- ...event?.target,
40
- value: value,
41
- name: props.name
42
- }
43
- } as TextFieldChangeEvent;
44
- props.onChange(e);
45
- }
46
- }, [props.value, props.onChange, props.name]);
47
-
48
- const internalOnChange = useCallback((event: TextFieldChangeEvent) => {
49
- const newValue = event.target.value;
50
- setInternalValue(newValue);
51
-
52
- if (timerRef.current) clearTimeout(timerRef.current);
53
-
54
- const eventCopy = {
55
- ...event,
56
- target: {
57
- ...event?.target,
58
- name: event?.target?.name
59
- }
60
- };
61
-
62
- timerRef.current = setTimeout(() => {
63
- flushChange(newValue, eventCopy as TextFieldChangeEvent);
64
- }, 150);
65
- }, [flushChange]);
66
-
67
- const internalOnBlur = useCallback((event: React.FocusEvent<HTMLInputElement>) => {
68
- flushChange(internalValue, event as TextFieldChangeEvent);
69
- props.onBlur?.(event);
70
- }, [internalValue, flushChange, props.onBlur]);
71
-
72
- return <TextField {...props}
73
- onChange={internalOnChange}
74
- onBlur={internalOnBlur}
75
- value={internalValue as T}/>;
76
- }