Skip to content

Commit 7c06dd9

Browse files
committed
Upgrading some packages with date-fns no op shim and replaced usage
1 parent 662a8c2 commit 7c06dd9

10 files changed

Lines changed: 245 additions & 155 deletions

File tree

frontend/bun.lock

Lines changed: 28 additions & 116 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

frontend/package.json

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -67,8 +67,7 @@
6767
"picomatch": "^2.3.2",
6868
"webpack": "^5.104.1",
6969
"@tootallnate/once": "^3.0.1",
70-
"sirv": "^3.0.2",
71-
"date-fns-tz": "^3.2.0"
70+
"sirv": "^3.0.2"
7271
},
7372
"dependencies": {
7473
"@a2a-js/sdk": "^0.3.13",
@@ -111,8 +110,8 @@
111110
"ai": "^6.0.168",
112111
"array-move": "^4.0.0",
113112
"chakra-react-select": "5.0.5",
114-
"class-variance-authority": "^0.7.0",
115-
"clsx": "^2.0.0",
113+
"class-variance-authority": "^0.7.1",
114+
"clsx": "^2.1.1",
116115
"cmdk": "^1.0.0",
117116
"date-fns": "^4.0.0",
118117
"dexie": "^4.2.1",
@@ -126,7 +125,7 @@
126125
"json-bigint": "^1.0.0",
127126
"jwt-decode": "^4.0.0",
128127
"lottie-react": "^2.0.0",
129-
"lucide-react": "^1.0.0",
128+
"lucide-react": "^1.7.0",
130129
"moment": "^2.30.1",
131130
"monaco-editor": "^0.55.0",
132131
"monaco-editor-webpack-plugin": "^7.1.1",
@@ -140,7 +139,7 @@
140139
"react": "^18.3.1",
141140
"react-beautiful-dnd": "^13.1.1",
142141
"react-compiler-runtime": "^1.0.0",
143-
"react-day-picker": "^9.0.0",
142+
"react-day-picker": "^9.14.0",
144143
"react-dom": "^18.3.1",
145144
"react-draggable": "^4.5.0",
146145
"react-dropzone": "^15.0.0",
@@ -157,7 +156,7 @@
157156
"sonner": "^2.0.0",
158157
"stacktrace-js": "^2.0.2",
159158
"streamdown": "^2.5.0",
160-
"tailwind-merge": "^3.0.0",
159+
"tailwind-merge": "^3.5.0",
161160
"tailwindcss": "^4.2.4",
162161
"tokenlens": "^1.3.1",
163162
"use-stick-to-bottom": "^1.1.1",
@@ -248,8 +247,5 @@
248247
"@tootallnate/once": "^3.0.1",
249248
"sirv": "^3.0.2",
250249
"**/webpack-bundle-analyzer/sirv": "^3.0.2"
251-
},
252-
"patchedDependencies": {
253-
"@redpanda-data/ui@4.2.0": "patches/@redpanda-data%2Fui@4.2.0.patch"
254250
}
255251
}

frontend/rsbuild.config.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import MonacoWebpackPlugin from 'monaco-editor-webpack-plugin';
1313
import { moduleFederationConfig } from './module-federation.config';
1414
import { HEAP_APP_ID } from './src/heap/heap.helper';
1515
import { HUBSPOT_PORTAL_ID } from './src/hubspot/hubspot.helper';
16+
import path from 'node:path';
1617

1718
const { publicVars, rawPublicVars } = loadEnv({ prefixes: ['REACT_APP_'] });
1819

@@ -138,6 +139,15 @@ export default defineConfig({
138139
/* resolve symlinks so the proto generate code can be built. */
139140
config.resolve.symlinks = false;
140141

142+
// `@redpanda-data/ui@4.2.0` has module-level imports from `date-fns-tz` v2
143+
// paths in its bundled dist. We replaced `<DateTimeInput>` (the only caller)
144+
// with `components/ui/date-time-input`, so these imports are dead — but
145+
// they still need to resolve at link time. Point them at no-op stubs.
146+
Object.assign(config.resolve.alias as Record<string, string>, {
147+
'date-fns-tz$': path.resolve(__dirname, 'src/utils/vendor/date-fns-tz-shim.ts'),
148+
'date-fns-tz/zonedTimeToUtc$': path.resolve(__dirname, 'src/utils/vendor/zonedTimeToUtc.ts'),
149+
});
150+
141151
config.output.publicPath = 'auto';
142152

143153
// Prevent rebuild loop by ignoring generated route tree file

frontend/src/components/misc/kowl-time-picker.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@
99
* by the Apache License, Version 2.0
1010
*/
1111

12-
import { Box, DateTimeInput } from '@redpanda-data/ui';
12+
import { Box } from '@redpanda-data/ui';
13+
import { DateTimeInput } from 'components/ui/date-time-input';
1314
import { useState } from 'react';
1415

1516
export function KowlTimePicker(props: { valueUtcMs: number; onChange: (utcMs: number) => void; disabled?: boolean }) {
@@ -18,6 +19,7 @@ export function KowlTimePicker(props: { valueUtcMs: number; onChange: (utcMs: nu
1819
return (
1920
<Box maxW={300}>
2021
<DateTimeInput
22+
disabled={props.disabled}
2123
onChange={(value) => {
2224
setTimestampUtcMs(value);
2325
props.onChange(value);

frontend/src/components/pages/admin/admin-debug-bundle.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@ import {
2222
Button,
2323
Checkbox,
2424
ConfirmModal,
25-
DateTimeInput,
2625
Flex,
2726
FormField,
2827
Grid,
@@ -35,6 +34,7 @@ import {
3534
Text,
3635
} from '@redpanda-data/ui';
3736
import { TrashIcon } from 'components/icons';
37+
import { DateTimeInput } from 'components/ui/date-time-input';
3838

3939
import {
4040
type CreateDebugBundleRequest,

frontend/src/components/pages/topics/Tab.Messages/forms/start-offset-date-time-picker.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
* by the Apache License, Version 2.0
1010
*/
1111

12-
import { DateTimeInput } from '@redpanda-data/ui';
12+
import { DateTimeInput } from 'components/ui/date-time-input';
1313
import { useEffect, useRef, useState } from 'react';
1414

1515
import { useTopicSettingsStore } from '../../../../../stores/topic-settings-store';

frontend/src/components/redpanda-ui/components/checkbox.tsx

Lines changed: 30 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -7,17 +7,28 @@ import React from 'react';
77

88
import { cn, type SharedProps } from '../lib/utils';
99

10+
// Path-draw animation driven by CSS:
11+
// - pathLength={1} normalizes the stroke-dash coordinate space to 0..1
12+
// regardless of actual path length.
13+
// - Hidden: stroke-dashoffset 1 + opacity 0 → path is shifted off-screen.
14+
// - Visible (data-visible="true"): stroke-dashoffset 0 + opacity 1, with a
15+
// 100ms delay so the box-fill transition leads the stroke draw-in slightly.
16+
// - The browser's native CSS transition handles the tween, so it's immune to
17+
// React re-render frequency in controlled-mode parents.
18+
const pathDrawClassName =
19+
'[stroke-dasharray:1] [stroke-dashoffset:1] opacity-0 transition-[stroke-dashoffset,opacity] duration-200 ease-out data-[visible=true]:[stroke-dashoffset:0] data-[visible=true]:opacity-100 data-[visible=true]:delay-[100ms]';
20+
1021
const checkboxVariants = cva(
1122
'peer flex size-5 shrink-0 cursor-pointer items-center justify-center rounded-sm border-2 transition-colors duration-500 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50',
1223
{
1324
variants: {
1425
variant: {
1526
primary:
16-
'!border-input data-[state=checked]:border-primary data-[state=checked]:bg-primary data-[state=checked]:text-inverse',
27+
'!border-input data-[state=checked]:border-primary data-[state=indeterminate]:border-primary data-[state=checked]:bg-primary data-[state=indeterminate]:bg-primary data-[state=checked]:text-inverse data-[state=indeterminate]:text-inverse',
1728
secondary:
18-
'!border-input data-[state=checked]:border-secondary data-[state=checked]:bg-secondary data-[state=checked]:text-inverse',
29+
'!border-input data-[state=checked]:border-secondary data-[state=indeterminate]:border-secondary data-[state=checked]:bg-secondary data-[state=indeterminate]:bg-secondary data-[state=checked]:text-inverse data-[state=indeterminate]:text-inverse',
1930
outline:
20-
'!border-input data-[state=checked]:border-foreground data-[state=checked]:bg-transparent data-[state=checked]:text-foreground',
31+
'!border-input data-[state=checked]:border-foreground data-[state=indeterminate]:border-foreground data-[state=checked]:bg-transparent data-[state=indeterminate]:bg-transparent data-[state=checked]:text-foreground data-[state=indeterminate]:text-foreground',
2132
},
2233
},
2334
defaultVariants: {
@@ -71,6 +82,7 @@ const Checkbox = React.forwardRef<HTMLButtonElement, CheckboxProps>(
7182
(defaultChecked as unknown) === 'indeterminate' ? false : (defaultChecked as boolean | undefined);
7283

7384
const dataState = isIndeterminate ? 'indeterminate' : isChecked ? 'checked' : 'unchecked';
85+
const showCheckmark = isChecked === true && !isIndeterminate;
7486

7587
const renderRoot = React.useCallback(
7688
// biome-ignore lint/suspicious/noExplicitAny: Base UI render merges Root attrs for the consumer element
@@ -88,48 +100,40 @@ const Checkbox = React.forwardRef<HTMLButtonElement, CheckboxProps>(
88100
keepMounted
89101
// biome-ignore lint/suspicious/noExplicitAny: Base UI render merges Indicator attrs for the consumer element
90102
render={(indicatorProps: Record<string, any>) => (
91-
<motion.svg
103+
<svg
92104
{...indicatorProps}
93-
animate={isChecked === true ? 'checked' : 'unchecked'}
94105
className="size-3.5"
95106
data-slot="checkbox-indicator"
96107
data-state={dataState}
97108
fill="none"
98-
initial="unchecked"
99109
stroke="currentColor"
100110
strokeWidth="3.5"
101111
viewBox="0 0 24 24"
102112
xmlns="http://www.w3.org/2000/svg"
103113
>
104114
<title>Checkbox</title>
105-
<motion.path
115+
<path
116+
className={pathDrawClassName}
106117
d="M4.5 12.75l6 6 9-13.5"
118+
data-visible={showCheckmark}
119+
pathLength={1}
120+
strokeLinecap="round"
121+
strokeLinejoin="round"
122+
/>
123+
<path
124+
className={pathDrawClassName}
125+
d="M5 12h14"
126+
data-visible={isIndeterminate}
127+
pathLength={1}
107128
strokeLinecap="round"
108129
strokeLinejoin="round"
109-
variants={{
110-
checked: {
111-
pathLength: 1,
112-
opacity: 1,
113-
transition: {
114-
duration: 0.2,
115-
delay: 0.2,
116-
},
117-
},
118-
unchecked: {
119-
pathLength: 0,
120-
opacity: 0,
121-
transition: {
122-
duration: 0.2,
123-
},
124-
},
125-
}}
126130
/>
127-
</motion.svg>
131+
</svg>
128132
)}
129133
/>
130134
</motion.button>
131135
),
132-
[className, dataState, isChecked, testId, variant]
136+
[className, dataState, isIndeterminate, showCheckmark, testId, variant]
133137
);
134138

135139
return (
Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
/**
2+
* Copyright 2026 Redpanda Data, Inc.
3+
*
4+
* Use of this software is governed by the Business Source License
5+
* included in the file https://github.com/redpanda-data/redpanda/blob/dev/licenses/bsl.md
6+
*
7+
* As of the Change Date specified in that file, in accordance with
8+
* the Business Source License, use of this software will be governed
9+
* by the Apache License, Version 2.0
10+
*/
11+
12+
import { Button } from 'components/redpanda-ui/components/button';
13+
import { Calendar } from 'components/redpanda-ui/components/calendar';
14+
import { Input } from 'components/redpanda-ui/components/input';
15+
import { Popover, PopoverContent, PopoverTrigger } from 'components/redpanda-ui/components/popover';
16+
import { cn } from 'components/redpanda-ui/lib/utils';
17+
import { CalendarIcon } from 'lucide-react';
18+
import { useMemo, useState } from 'react';
19+
20+
const DATE_PART_LENGTH = 10; // YYYY-MM-DD
21+
const TIME_PART_LENGTH = 8; // HH:mm:ss
22+
23+
const pad = (n: number, len = 2) => String(n).padStart(len, '0');
24+
25+
const toLocalDate = (utcMs: number) => new Date(utcMs);
26+
27+
const formatDateLabel = (utcMs: number) => {
28+
const d = toLocalDate(utcMs);
29+
return `${pad(d.getFullYear(), 4)}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}`;
30+
};
31+
32+
const formatTimeLabel = (utcMs: number) => {
33+
const d = toLocalDate(utcMs);
34+
return `${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}`;
35+
};
36+
37+
const composeUtcMs = (datePart: string, timePart: string): number | null => {
38+
if (datePart.length !== DATE_PART_LENGTH) {
39+
return null;
40+
}
41+
const normalizedTime = timePart.length === 5 ? `${timePart}:00` : timePart;
42+
if (normalizedTime.length !== TIME_PART_LENGTH) {
43+
return null;
44+
}
45+
const ms = Date.parse(`${datePart}T${normalizedTime}`);
46+
return Number.isFinite(ms) ? ms : null;
47+
};
48+
49+
type DateTimeInputProps = {
50+
value: number | undefined;
51+
onChange: (utcMs: number) => void;
52+
disabled?: boolean;
53+
className?: string;
54+
'data-testid'?: string;
55+
};
56+
57+
/**
58+
* Local date+time picker. Replaces `<DateTimeInput>` from `@redpanda-data/ui`,
59+
* which depended on date-fns-tz@2 and forced a build-time shim. Uses the
60+
* Calendar + Popover + Input registry components and works in the user's local
61+
* timezone — value/onChange remain UTC milliseconds for parity with the old API.
62+
*/
63+
export const DateTimeInput = ({ value, onChange, disabled, className, ...rest }: DateTimeInputProps) => {
64+
const [open, setOpen] = useState(false);
65+
const effectiveValue = value ?? Date.now();
66+
67+
const dateLabel = useMemo(() => formatDateLabel(effectiveValue), [effectiveValue]);
68+
const timeLabel = useMemo(() => formatTimeLabel(effectiveValue), [effectiveValue]);
69+
70+
const setDate = (next: Date | undefined) => {
71+
if (!next) {
72+
return;
73+
}
74+
const ms = composeUtcMs(
75+
`${pad(next.getFullYear(), 4)}-${pad(next.getMonth() + 1)}-${pad(next.getDate())}`,
76+
timeLabel
77+
);
78+
if (ms !== null) {
79+
onChange(ms);
80+
}
81+
};
82+
83+
const setTime = (nextTime: string) => {
84+
const ms = composeUtcMs(dateLabel, nextTime);
85+
if (ms !== null) {
86+
onChange(ms);
87+
}
88+
};
89+
90+
return (
91+
<div className={cn('flex items-center gap-2', className)} data-testid={rest['data-testid']}>
92+
<Popover onOpenChange={setOpen} open={open}>
93+
<PopoverTrigger>
94+
<Button
95+
className={cn('w-[160px] justify-start font-normal', !value && 'text-muted-foreground')}
96+
disabled={disabled}
97+
type="button"
98+
variant="outline"
99+
>
100+
<CalendarIcon className="mr-2 size-4" />
101+
{dateLabel}
102+
</Button>
103+
</PopoverTrigger>
104+
<PopoverContent align="start" className="w-auto p-0">
105+
<Calendar
106+
mode="single"
107+
onSelect={(d) => {
108+
setDate(d);
109+
setOpen(false);
110+
}}
111+
selected={toLocalDate(effectiveValue)}
112+
/>
113+
</PopoverContent>
114+
</Popover>
115+
<Input
116+
className="w-[120px]"
117+
disabled={disabled}
118+
onChange={(e) => setTime(e.target.value)}
119+
step={1}
120+
type="time"
121+
value={timeLabel}
122+
/>
123+
</div>
124+
);
125+
};
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
/**
2+
* Build-time stub for `date-fns-tz`.
3+
*
4+
* `@redpanda-data/ui@4.2.0` ships a single bundled `dist/index.js` with
5+
* top-level imports from `date-fns-tz` (v2 names: `utcToZonedTime`,
6+
* `zonedTimeToUtc`). Those imports must resolve at link time even though
7+
* we no longer use `<DateTimeInput>` — the only component that actually
8+
* called them. v3 of date-fns-tz renamed both functions, so we can't
9+
* point at it directly.
10+
*
11+
* This module satisfies the link by exporting matching names. The bodies
12+
* are unreachable: the only callers live inside `DateTimePicker` /
13+
* `DateTimeInput`, which are never invoked because we replaced their
14+
* usage with `components/ui/date-time-input`. Drop this file (and the
15+
* matching aliases in `rsbuild.config.ts`) once `@redpanda-data/ui` is
16+
* upgraded past v4.2.0.
17+
*/
18+
19+
const unreachable = () => {
20+
throw new Error('date-fns-tz shim: unreachable — DateTimeInput was replaced; this should not be called.');
21+
};
22+
23+
export const format = unreachable;
24+
export const formatInTimeZone = unreachable;
25+
export const fromZonedTime = unreachable;
26+
export const getTimezoneOffset = unreachable;
27+
export const toDate = unreachable;
28+
export const toZonedTime = unreachable;
29+
export const utcToZonedTime = unreachable;
30+
export const zonedTimeToUtc = unreachable;
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
/**
2+
* Build-time stub for the v2 deep path `date-fns-tz/zonedTimeToUtc`.
3+
* `@redpanda-data/ui@4.2.0` does `import zonedTimeToUtc from 'date-fns-tz/zonedTimeToUtc'`,
4+
* which only existed in v2. We replaced the only call site (`<DateTimeInput>`),
5+
* so the body is unreachable.
6+
*/
7+
const unreachable = () => {
8+
throw new Error('date-fns-tz/zonedTimeToUtc shim: unreachable — DateTimeInput was replaced.');
9+
};
10+
11+
export default unreachable;

0 commit comments

Comments
 (0)