aplance-ui 1.0.1

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 (132) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +29 -0
  3. package/dist/api.d.ts +175 -0
  4. package/dist/api.js +1954 -0
  5. package/dist/api.js.map +1 -0
  6. package/dist/frameworks/astro/astro.config.snippet.md +23 -0
  7. package/dist/frameworks/astro/postcss.config.mjs.template +20 -0
  8. package/dist/frameworks/astro/vite-inline.snippet.md +22 -0
  9. package/dist/frameworks/electron-vite/vite.config.snippet.md +44 -0
  10. package/dist/frameworks/next/layout-import.snippet.md +19 -0
  11. package/dist/frameworks/next/next.config.snippet.md +15 -0
  12. package/dist/frameworks/next/postcss.config.js.template +16 -0
  13. package/dist/frameworks/tanstack-start/postcss.config.ts.template +17 -0
  14. package/dist/frameworks/tanstack-start/vite-inline.snippet.md +22 -0
  15. package/dist/frameworks/vite-react/postcss.config.ts.template +16 -0
  16. package/dist/frameworks/vite-react/vite-inline.snippet.md +20 -0
  17. package/dist/frameworks/vite-react/vite.config.ts.template +48 -0
  18. package/dist/index.js +2047 -0
  19. package/dist/index.js.map +1 -0
  20. package/package.json +63 -0
  21. package/registry/components/primitives/icon/icon.module.css +63 -0
  22. package/registry/components/primitives/icon/icon.tsx +53 -0
  23. package/registry/components/primitives/modal/modal-context.ts +19 -0
  24. package/registry/components/primitives/modal/modal.module.css +415 -0
  25. package/registry/components/primitives/modal/modal.tsx +550 -0
  26. package/registry/components/primitives/popover/popover-context.ts +28 -0
  27. package/registry/components/primitives/popover/popover.module.css +28 -0
  28. package/registry/components/primitives/popover/popover.tsx +465 -0
  29. package/registry/components/ui/accordion/accordion.module.css +208 -0
  30. package/registry/components/ui/accordion/accordion.tsx +327 -0
  31. package/registry/components/ui/avatar/avatar.module.css +32 -0
  32. package/registry/components/ui/avatar/avatar.tsx +37 -0
  33. package/registry/components/ui/badge/badge.module.css +103 -0
  34. package/registry/components/ui/badge/badge.tsx +29 -0
  35. package/registry/components/ui/button/button.module.css +216 -0
  36. package/registry/components/ui/button/button.tsx +133 -0
  37. package/registry/components/ui/chart/chart.module.css +168 -0
  38. package/registry/components/ui/chart/chart.tsx +1293 -0
  39. package/registry/components/ui/chart/types.ts +210 -0
  40. package/registry/components/ui/chart/utils.ts +59 -0
  41. package/registry/components/ui/copy-button/copy-button.module.css +21 -0
  42. package/registry/components/ui/copy-button/copy-button.tsx +123 -0
  43. package/registry/components/ui/countdown/countdown.module.css +5 -0
  44. package/registry/components/ui/countdown/countdown.tsx +152 -0
  45. package/registry/components/ui/data-table/checkbox-column.tsx +59 -0
  46. package/registry/components/ui/data-table/data-table.module.css +594 -0
  47. package/registry/components/ui/data-table/data-table.tsx +1109 -0
  48. package/registry/components/ui/data-table/filter-bar.tsx +33 -0
  49. package/registry/components/ui/data-table/pagination-bar.tsx +125 -0
  50. package/registry/components/ui/data-table/types.ts +594 -0
  51. package/registry/components/ui/data-table/useDataTable.tsx +573 -0
  52. package/registry/components/ui/data-table/utils.ts +445 -0
  53. package/registry/components/ui/empty-state/empty-state.module.css +26 -0
  54. package/registry/components/ui/empty-state/empty-state.tsx +32 -0
  55. package/registry/components/ui/input-fields/color-picker/color-palette.module.css +102 -0
  56. package/registry/components/ui/input-fields/color-picker/color-palette.tsx +92 -0
  57. package/registry/components/ui/input-fields/color-picker/color-picker.module.css +29 -0
  58. package/registry/components/ui/input-fields/color-picker/color-picker.tsx +168 -0
  59. package/registry/components/ui/input-fields/common/field-message/field-message.module.css +28 -0
  60. package/registry/components/ui/input-fields/common/field-message/field-message.tsx +39 -0
  61. package/registry/components/ui/input-fields/common/input-field-common.module.css +318 -0
  62. package/registry/components/ui/input-fields/common/input-field-native.module.css +267 -0
  63. package/registry/components/ui/input-fields/common/picker-popover-common.module.css +40 -0
  64. package/registry/components/ui/input-fields/common/select-button/select-button.module.css +9 -0
  65. package/registry/components/ui/input-fields/common/select-button/select-button.tsx +171 -0
  66. package/registry/components/ui/input-fields/common/types.ts +38 -0
  67. package/registry/components/ui/input-fields/custom-select/custom-select.module.css +56 -0
  68. package/registry/components/ui/input-fields/custom-select/custom-select.tsx +403 -0
  69. package/registry/components/ui/input-fields/date-picker/calendar.module.css +311 -0
  70. package/registry/components/ui/input-fields/date-picker/calendar.tsx +186 -0
  71. package/registry/components/ui/input-fields/date-picker/date-picker.module.css +57 -0
  72. package/registry/components/ui/input-fields/date-picker/date-picker.tsx +945 -0
  73. package/registry/components/ui/input-fields/date-picker/types.ts +102 -0
  74. package/registry/components/ui/input-fields/date-picker/utils.ts +236 -0
  75. package/registry/components/ui/input-fields/emoji-picker/emoji-palette.module.css +90 -0
  76. package/registry/components/ui/input-fields/emoji-picker/emoji-palette.tsx +168 -0
  77. package/registry/components/ui/input-fields/emoji-picker/emoji-picker.module.css +8 -0
  78. package/registry/components/ui/input-fields/emoji-picker/emoji-picker.tsx +102 -0
  79. package/registry/components/ui/input-fields/emoji-picker/utils.ts +505 -0
  80. package/registry/components/ui/input-fields/field-group/field-group.module.css +86 -0
  81. package/registry/components/ui/input-fields/field-group/field-group.tsx +86 -0
  82. package/registry/components/ui/input-fields/input/input.tsx +319 -0
  83. package/registry/components/ui/input-fields/media-input/kind-icons.tsx +62 -0
  84. package/registry/components/ui/input-fields/media-input/media-input.module.css +294 -0
  85. package/registry/components/ui/input-fields/media-input/media-input.tsx +327 -0
  86. package/registry/components/ui/input-fields/media-input/media-preview.tsx +101 -0
  87. package/registry/components/ui/input-fields/media-input/types.ts +129 -0
  88. package/registry/components/ui/input-fields/media-input/useMediaUpload.tsx +302 -0
  89. package/registry/components/ui/input-fields/media-input/utils.ts +325 -0
  90. package/registry/components/ui/input-fields/otp-input/otp-input.module.css +24 -0
  91. package/registry/components/ui/input-fields/otp-input/otp-input.tsx +291 -0
  92. package/registry/components/ui/input-fields/select/select.tsx +118 -0
  93. package/registry/components/ui/input-fields/switch/switch.module.css +70 -0
  94. package/registry/components/ui/input-fields/switch/switch.tsx +59 -0
  95. package/registry/components/ui/input-fields/textarea/textarea.tsx +133 -0
  96. package/registry/components/ui/input-fields/time-picker/time-picker.module.css +57 -0
  97. package/registry/components/ui/input-fields/time-picker/time-picker.tsx +437 -0
  98. package/registry/components/ui/loader/loader.module.css +156 -0
  99. package/registry/components/ui/loader/loader.tsx +102 -0
  100. package/registry/components/ui/stepper/stepper.module.css +294 -0
  101. package/registry/components/ui/stepper/stepper.tsx +197 -0
  102. package/registry/components/ui/tabs/tabs.module.css +150 -0
  103. package/registry/components/ui/tabs/tabs.tsx +134 -0
  104. package/registry/components/ui/toast/toast.module.css +159 -0
  105. package/registry/components/ui/toast/toast.tsx +104 -0
  106. package/registry/components/ui/tooltip/tooltip.module.css +82 -0
  107. package/registry/components/ui/tooltip/tooltip.tsx +72 -0
  108. package/registry/global.d.ts +4 -0
  109. package/registry/hooks/useFocusOutside.tsx +33 -0
  110. package/registry/hooks/useResizeObserver.tsx +32 -0
  111. package/registry/lib/dom.ts +25 -0
  112. package/registry/lib/string.ts +23 -0
  113. package/registry/registry.json +719 -0
  114. package/registry/styles/app.css +240 -0
  115. package/registry/styles/components.css +78 -0
  116. package/registry/styles/fonts/geist-regular.woff2 +0 -0
  117. package/registry/styles/globals.css +7 -0
  118. package/registry/styles/media.css +25 -0
  119. package/registry/styles/utilities.css +90 -0
  120. package/registry/themes/colors/blue.css +37 -0
  121. package/registry/themes/colors/green.css +37 -0
  122. package/registry/themes/colors/rose.css +37 -0
  123. package/registry/themes/colors/slate.css +37 -0
  124. package/registry/themes/colors/stone.css +37 -0
  125. package/registry/themes/colors/violet.css +37 -0
  126. package/registry/themes/colors/zinc.css +37 -0
  127. package/registry/themes/radius/pill.css +8 -0
  128. package/registry/themes/radius/rounded.css +8 -0
  129. package/registry/themes/radius/sharp.css +8 -0
  130. package/registry/themes/spacing/compact.css +17 -0
  131. package/registry/themes/spacing/spacious.css +17 -0
  132. package/registry/tsconfig.json +25 -0
@@ -0,0 +1,57 @@
1
+ .popover_menu {
2
+ min-width: 13.25rem;
3
+ }
4
+
5
+ .columns {
6
+ display: flex;
7
+ align-items: stretch;
8
+ gap: var(--spacing-1);
9
+ padding: var(--spacing-3);
10
+ }
11
+
12
+ .column {
13
+ flex: 1;
14
+ min-width: 3.75rem;
15
+ max-height: 13rem;
16
+ overflow-y: auto;
17
+ display: flex;
18
+ flex-direction: column;
19
+ gap: var(--spacing-2);
20
+ scrollbar-width: none;
21
+
22
+ &::-webkit-scrollbar {
23
+ display: none;
24
+ }
25
+ }
26
+
27
+ .column_separator {
28
+ display: flex;
29
+ align-items: center;
30
+ justify-content: center;
31
+ padding: 0 var(--spacing-1);
32
+ font-size: var(--font-size-base);
33
+ color: var(--color-muted-foreground);
34
+ font-weight: var(--font-weight-medium);
35
+ flex-shrink: 0;
36
+ align-self: center;
37
+ }
38
+
39
+ .column_separator_sub {
40
+ opacity: 0.8;
41
+ }
42
+
43
+ .column_meridiem {
44
+ min-width: 3rem;
45
+ }
46
+
47
+ .column_item {
48
+ width: 100%;
49
+ justify-content: center !important;
50
+ flex-shrink: 0;
51
+ }
52
+
53
+ .column_item_meridiem {
54
+ letter-spacing: var(--letter-spacing-wide);
55
+ font-weight: var(--font-weight-medium);
56
+ }
57
+
@@ -0,0 +1,437 @@
1
+ import {
2
+ type ComponentProps,
3
+ useCallback,
4
+ useEffect,
5
+ useId,
6
+ useMemo,
7
+ useRef,
8
+ useState,
9
+ } from "react";
10
+ import clsx from "clsx";
11
+ import { IconClock } from "@tabler/icons-react";
12
+ import {
13
+ Popover,
14
+ PopoverMenu,
15
+ PopoverTrigger,
16
+ } from "../../../primitives/popover/popover";
17
+ import { Icon } from "../../../primitives/icon/icon";
18
+ import { Button } from "../../button/button";
19
+ import { FieldMessage } from "../common/field-message/field-message";
20
+ import sCommon from "../common/input-field-common.module.css";
21
+ import sPickerCommon from "../common/picker-popover-common.module.css";
22
+ import {
23
+ SelectButton,
24
+ type SelectButtonProps,
25
+ } from "../common/select-button/select-button";
26
+ import type { BaseInputFieldProps } from "../common/types";
27
+ import s from "./time-picker.module.css";
28
+
29
+ export type TimePickerProps = BaseInputFieldProps & {
30
+ /** Controlled value in "HH:mm" format. */
31
+ value?: string;
32
+ /** Initial value in "HH:mm" format for uncontrolled usage. */
33
+ defaultValue?: string;
34
+ /** Called with the new "HH:mm" string, or undefined when cleared. */
35
+ onChange?: (value: string | undefined) => void;
36
+
37
+ placeholder?: string;
38
+
39
+ /** Display mode for hour selection. Value emitted by onChange remains "HH:mm" (24h). */
40
+ timeFormat?: "24h" | "12h";
41
+
42
+ /** Minute column increment. Common values: 1, 5, 10, 15, 30. Default: 1. */
43
+ minuteStep?: number;
44
+
45
+ showClear?: boolean;
46
+ /** Show popover footer actions (Clear/Done). Default: true. */
47
+ showActionFooter?: boolean;
48
+ onClear?: () => void;
49
+ onBlur?: () => void;
50
+
51
+ popover?: Omit<ComponentProps<typeof Popover>, "children">;
52
+ popoverTrigger?: ComponentProps<typeof PopoverTrigger>;
53
+ popoverMenu?: ComponentProps<typeof PopoverMenu>;
54
+ selectButton?: Omit<SelectButtonProps, "children" | "onClear">;
55
+ };
56
+
57
+ const pad2 = (n: number) => String(n).padStart(2, "0");
58
+
59
+ const HOURS_24 = Array.from({ length: 24 }, (_, i) => i);
60
+ const HOURS_12 = Array.from({ length: 12 }, (_, i) => i + 1);
61
+ const MERIDIEMS = ["AM", "PM"] as const;
62
+
63
+ type Meridiem = (typeof MERIDIEMS)[number];
64
+
65
+ const parseTimeValue = (
66
+ value: string | undefined,
67
+ ): { hours: number; minutes: number } | undefined => {
68
+ if (!value) return undefined;
69
+ const match = /^(\d{1,2}):(\d{2})$/.exec(value.trim());
70
+ if (!match) return undefined;
71
+ const hours = Number(match[1]);
72
+ const minutes = Number(match[2]);
73
+ if (hours < 0 || hours > 23 || minutes < 0 || minutes > 59) return undefined;
74
+ return { hours, minutes };
75
+ };
76
+
77
+ const formatTimeValue = (hours: number, minutes: number): string =>
78
+ `${pad2(hours)}:${pad2(minutes)}`;
79
+
80
+ const toMeridiem = (hours24: number): Meridiem =>
81
+ hours24 >= 12 ? "PM" : "AM";
82
+
83
+ const toHour12 = (hours24: number): number => {
84
+ const normalized = hours24 % 12;
85
+ return normalized === 0 ? 12 : normalized;
86
+ };
87
+
88
+ const toHour24 = (hours12: number, meridiem: Meridiem): number => {
89
+ const normalized = hours12 % 12;
90
+ return meridiem === "PM" ? normalized + 12 : normalized;
91
+ };
92
+
93
+ const scrollColumnToSelected = (col: HTMLDivElement | null) => {
94
+ if (!col) return;
95
+ const item = col.querySelector<HTMLElement>("[data-selected]");
96
+ if (!item) {
97
+ col.scrollTop = 0;
98
+ return;
99
+ }
100
+ col.scrollTop = item.offsetTop - col.offsetHeight / 2 + item.offsetHeight / 2;
101
+ };
102
+
103
+ export const TimePicker = (props: TimePickerProps) => {
104
+ const {
105
+ label,
106
+ className,
107
+ style,
108
+ id,
109
+ info,
110
+ errors,
111
+ layout = "default",
112
+ layoutDirection = "default",
113
+
114
+ placeholder = "Select time...",
115
+ timeFormat = "24h",
116
+ minuteStep = 1,
117
+
118
+ disabled = false,
119
+ readOnly = false,
120
+ required = false,
121
+
122
+ showClear = true,
123
+ showActionFooter = true,
124
+ onClear,
125
+ onBlur,
126
+ onChange,
127
+
128
+ popover,
129
+ popoverTrigger,
130
+ popoverMenu,
131
+ selectButton,
132
+ } = props;
133
+
134
+ const isControlled = "value" in props;
135
+
136
+ const generatedId = useId();
137
+ const inputId = id ?? generatedId;
138
+
139
+ const is12Hour = timeFormat === "12h";
140
+
141
+ const resolvedMinuteStep = useMemo(() => {
142
+ const normalized = Math.floor(minuteStep);
143
+ if (!Number.isFinite(normalized) || normalized < 1) return 1;
144
+ return Math.min(59, normalized);
145
+ }, [minuteStep]);
146
+
147
+ const minutes = useMemo(
148
+ () =>
149
+ Array.from(
150
+ { length: Math.ceil(60 / resolvedMinuteStep) },
151
+ (_, i) => i * resolvedMinuteStep,
152
+ ).filter((m) => m < 60),
153
+ [resolvedMinuteStep],
154
+ );
155
+
156
+ const [internalValue, setInternalValue] = useState<string | undefined>(() =>
157
+ "defaultValue" in props ? props.defaultValue : undefined,
158
+ );
159
+
160
+ const currentValue = isControlled ? props.value : internalValue;
161
+ const parsed = useMemo(() => parseTimeValue(currentValue), [currentValue]);
162
+ const hasValue = Boolean(currentValue);
163
+
164
+ const [isOpen, setIsOpen] = useState(false);
165
+
166
+ const selectedHour =
167
+ parsed === undefined
168
+ ? undefined
169
+ : is12Hour
170
+ ? toHour12(parsed.hours)
171
+ : parsed.hours;
172
+ const selectedMinute = parsed?.minutes;
173
+ const selectedMeridiem = parsed ? toMeridiem(parsed.hours) : undefined;
174
+
175
+ const hoursColRef = useRef<HTMLDivElement>(null);
176
+ const minutesColRef = useRef<HTMLDivElement>(null);
177
+ const meridiemColRef = useRef<HTMLDivElement>(null);
178
+
179
+ // Scroll columns to the selected item when the popover opens
180
+ useEffect(() => {
181
+ if (!isOpen) return;
182
+ requestAnimationFrame(() => {
183
+ scrollColumnToSelected(hoursColRef.current);
184
+ scrollColumnToSelected(minutesColRef.current);
185
+ if (is12Hour) {
186
+ scrollColumnToSelected(meridiemColRef.current);
187
+ }
188
+ });
189
+ }, [isOpen, is12Hour]);
190
+
191
+ const commit = useCallback(
192
+ (h: number, m: number) => {
193
+ const next = formatTimeValue(h, m);
194
+ if (!isControlled) setInternalValue(next);
195
+ onChange?.(next);
196
+ },
197
+ [isControlled, onChange],
198
+ );
199
+
200
+ const handleSelectHour = useCallback(
201
+ (hourOption: number) => {
202
+ const nextHour = is12Hour
203
+ ? toHour24(hourOption, selectedMeridiem ?? "AM")
204
+ : hourOption;
205
+ commit(nextHour, selectedMinute ?? 0);
206
+ },
207
+ [commit, is12Hour, selectedMeridiem, selectedMinute],
208
+ );
209
+
210
+ const handleSelectMinute = useCallback(
211
+ (m: number) => {
212
+ commit(parsed?.hours ?? 0, m);
213
+ },
214
+ [commit, parsed?.hours],
215
+ );
216
+
217
+ const handleSelectMeridiem = useCallback(
218
+ (nextMeridiem: Meridiem) => {
219
+ const hour12 = parsed !== undefined ? toHour12(parsed.hours) : 12;
220
+ commit(toHour24(hour12, nextMeridiem), parsed?.minutes ?? 0);
221
+ },
222
+ [commit, parsed],
223
+ );
224
+
225
+ const handleClear = useCallback(() => {
226
+ if (!isControlled) setInternalValue(undefined);
227
+ onChange?.(undefined);
228
+ onClear?.();
229
+ }, [isControlled, onChange, onClear]);
230
+
231
+ const displayValue = useMemo(() => {
232
+ if (!hasValue) return placeholder;
233
+ if (!parsed) return currentValue ?? placeholder;
234
+ if (!is12Hour) return currentValue ?? placeholder;
235
+ return `${pad2(toHour12(parsed.hours))}:${pad2(parsed.minutes)} ${toMeridiem(parsed.hours)}`;
236
+ }, [currentValue, hasValue, is12Hour, parsed, placeholder]);
237
+
238
+ const popoverOnClose = popover?.onClose;
239
+ const handlePopoverClose = useCallback(() => {
240
+ popoverOnClose?.();
241
+ onBlur?.();
242
+ }, [onBlur, popoverOnClose]);
243
+
244
+ const {
245
+ className: popoverClassName,
246
+ offset: popoverOffset = 4,
247
+ ...popoverRest
248
+ } = popover ?? {};
249
+
250
+ const {
251
+ className: popoverTriggerClassName,
252
+ style: popoverTriggerStyle,
253
+ ...popoverTriggerRest
254
+ } = popoverTrigger ?? {};
255
+
256
+ const {
257
+ className: popoverMenuClassName,
258
+ style: popoverMenuStyle,
259
+ ...popoverMenuRest
260
+ } = popoverMenu ?? {};
261
+
262
+ return (
263
+ <div className={clsx(sCommon.container, className)} style={style}>
264
+ {label && (
265
+ <div
266
+ className={clsx(
267
+ sCommon.field_container,
268
+ layout !== "default" && sCommon[layout],
269
+ layout !== "default" &&
270
+ layoutDirection !== "default" &&
271
+ sCommon[layoutDirection],
272
+ )}
273
+ >
274
+ <label className={sCommon.label} htmlFor={inputId}>
275
+ {label}
276
+ </label>
277
+ </div>
278
+ )}
279
+
280
+ <Popover
281
+ closeOnPathChange={popover?.closeOnPathChange ?? true}
282
+ closeOnClickOutside={popover?.closeOnClickOutside ?? true}
283
+ animationDuration={popover?.animationDuration ?? 200}
284
+ position={popover?.position ?? "bottom"}
285
+ align={popover?.align ?? "start"}
286
+ isOpen={isOpen}
287
+ setIsOpen={setIsOpen}
288
+ className={clsx(sPickerCommon.picker_popover, popoverClassName)}
289
+ offset={popoverOffset}
290
+ {...popoverRest}
291
+ onClose={handlePopoverClose}
292
+ >
293
+ <PopoverTrigger
294
+ asChild
295
+ className={clsx(
296
+ sPickerCommon.picker_popover_trigger,
297
+ popoverTriggerClassName,
298
+ )}
299
+ style={popoverTriggerStyle}
300
+ disabled={disabled || readOnly}
301
+ {...popoverTriggerRest}
302
+ >
303
+ <SelectButton
304
+ id={inputId}
305
+ disabled={disabled || readOnly}
306
+ required={required}
307
+ className={clsx(
308
+ !hasValue && sPickerCommon.picker_value_placeholder,
309
+ selectButton?.className,
310
+ )}
311
+ style={selectButton?.style}
312
+ buttonVariant={selectButton?.buttonVariant}
313
+ buttonSize={selectButton?.buttonSize}
314
+ tooltip={selectButton?.tooltip}
315
+ tooltipPosition={selectButton?.tooltipPosition}
316
+ showClear={hasValue && showClear && !disabled && !readOnly}
317
+ onClear={(e: React.MouseEvent) => {
318
+ e.stopPropagation();
319
+ e.preventDefault();
320
+ handleClear();
321
+ }}
322
+ showChevron={false}
323
+ iconEnd={
324
+ <Icon aria-hidden="true" clickable>
325
+ <IconClock />
326
+ </Icon>
327
+ }
328
+ >
329
+ {displayValue}
330
+ </SelectButton>
331
+ </PopoverTrigger>
332
+
333
+ {!disabled && !readOnly && (
334
+ <PopoverMenu
335
+ className={clsx(
336
+ sPickerCommon.picker_popover_menu_base,
337
+ s.popover_menu,
338
+ popoverMenuClassName,
339
+ )}
340
+ style={popoverMenuStyle}
341
+ {...popoverMenuRest}
342
+ >
343
+ <div className={s.columns}>
344
+ <div className={s.column} ref={hoursColRef}>
345
+ {(is12Hour ? HOURS_12 : HOURS_24).map((h) => (
346
+ <Button
347
+ key={h}
348
+ variant="secondary"
349
+ size="default"
350
+ isActive={h === selectedHour}
351
+ // data-selected presence is used by scrollColumnToSelected
352
+ {...(h === selectedHour ? { "data-selected": "" } : {})}
353
+ className={s.column_item}
354
+ onClick={() => handleSelectHour(h)}
355
+ >
356
+ {pad2(h)}
357
+ </Button>
358
+ ))}
359
+ </div>
360
+
361
+ <div className={s.column_separator}>:</div>
362
+
363
+ <div className={s.column} ref={minutesColRef}>
364
+ {minutes.map((m) => (
365
+ <Button
366
+ key={m}
367
+ variant="secondary"
368
+ size="default"
369
+ isActive={m === selectedMinute}
370
+ {...(m === selectedMinute ? { "data-selected": "" } : {})}
371
+ className={s.column_item}
372
+ onClick={() => handleSelectMinute(m)}
373
+ >
374
+ {pad2(m)}
375
+ </Button>
376
+ ))}
377
+ </div>
378
+
379
+ {is12Hour && (
380
+ <>
381
+ <div className={clsx(s.column_separator, s.column_separator_sub)}>
382
+ :
383
+ </div>
384
+ <div className={clsx(s.column, s.column_meridiem)} ref={meridiemColRef}>
385
+ {MERIDIEMS.map((meridiem) => (
386
+ <Button
387
+ key={meridiem}
388
+ variant={
389
+ meridiem === selectedMeridiem ? "primary" : "secondary"
390
+ }
391
+ size="default"
392
+ {...(meridiem === selectedMeridiem
393
+ ? { "data-selected": "" }
394
+ : {})}
395
+ className={clsx(s.column_item, s.column_item_meridiem)}
396
+ onClick={() => handleSelectMeridiem(meridiem)}
397
+ >
398
+ {meridiem}
399
+ </Button>
400
+ ))}
401
+ </div>
402
+ </>
403
+ )}
404
+ </div>
405
+
406
+ {showActionFooter && (
407
+ <div className={sPickerCommon.picker_actions_footer}>
408
+ <Button
409
+ type="button"
410
+ variant="ghost"
411
+ size="sm"
412
+ disabled={!hasValue}
413
+ onClick={() => {
414
+ if (disabled || readOnly) return;
415
+ handleClear();
416
+ }}
417
+ >
418
+ Clear
419
+ </Button>
420
+ <Button
421
+ type="button"
422
+ variant="primary"
423
+ size="sm"
424
+ onClick={() => setIsOpen(false)}
425
+ >
426
+ Done
427
+ </Button>
428
+ </div>
429
+ )}
430
+ </PopoverMenu>
431
+ )}
432
+ </Popover>
433
+
434
+ <FieldMessage errors={errors} info={info} />
435
+ </div>
436
+ );
437
+ };
@@ -0,0 +1,156 @@
1
+ .spinner_loader {
2
+ display: inline-block;
3
+ width: var(--loader-size, 1.5rem);
4
+ height: var(--loader-size, 1.5rem);
5
+ color: var(--loader-color, var(--color-primary));
6
+
7
+ .loading_spinner {
8
+ width: 100%;
9
+ height: 100%;
10
+ animation: rotate 1.2s linear infinite;
11
+ }
12
+
13
+ .loading_path {
14
+ stroke: currentColor;
15
+ stroke-linecap: round;
16
+ stroke-width: 4.2;
17
+ stroke-dasharray: 90, 150;
18
+ stroke-dashoffset: 0;
19
+ animation: dash 1.4s ease-in-out infinite;
20
+ }
21
+ }
22
+
23
+ .dots_loader {
24
+ display: flex;
25
+ align-items: center;
26
+ justify-content: center;
27
+ gap: calc(var(--loader-size, 1rem) / 3.5);
28
+
29
+ span {
30
+ width: calc(var(--loader-size, 1rem) / 3);
31
+ height: calc(var(--loader-size, 1rem) / 3);
32
+ border-radius: var(--radius-full);
33
+ background: var(--loader-color, var(--color-muted-foreground));
34
+ animation: dot_pulse 1.1s ease-in-out infinite;
35
+
36
+ &:nth-child(2) {
37
+ animation-delay: 0.15s;
38
+ }
39
+
40
+ &:nth-child(3) {
41
+ animation-delay: 0.3s;
42
+ }
43
+ }
44
+ }
45
+
46
+ .skeleton_shape {
47
+ height: var(--spacing-10);
48
+ width: 100%;
49
+ background: linear-gradient(
50
+ 100deg,
51
+ color-mix(in srgb, var(--color-border) 82%, transparent) 20%,
52
+ color-mix(in srgb, var(--color-muted) 72%, transparent) 40%,
53
+ color-mix(in srgb, var(--color-border) 82%, transparent) 60%
54
+ )
55
+ var(--color-border);
56
+ background-size: 200% 100%;
57
+ border-radius: var(--radius-sm);
58
+ opacity: 0.5;
59
+ animation: skeleton_shimmer 0.75s ease-in-out infinite;
60
+ }
61
+
62
+ .page_loader {
63
+ display: flex;
64
+ align-items: center;
65
+ justify-content: center;
66
+ min-height: 12rem;
67
+ width: 100%;
68
+ }
69
+
70
+ .overlay_loader {
71
+ width: fit-content;
72
+ height: fit-content;
73
+ padding: var(--spacing-6);
74
+ border-radius: var(--radius-lg);
75
+ border: 1px solid var(--color-border);
76
+ display: flex;
77
+ align-items: center;
78
+ justify-content: center;
79
+ background-color: color-mix(
80
+ in srgb,
81
+ var(--color-background) 95%,
82
+ transparent
83
+ );
84
+ box-shadow: var(--shadow-lg);
85
+ position: fixed;
86
+ animation: delayed_load 0.1s linear 0.2s 1 forwards;
87
+ visibility: hidden;
88
+ opacity: 0;
89
+ top: 50%;
90
+ left: 50%;
91
+ transform: translate(-50%, -50%);
92
+ z-index: var(--z-index-modal);
93
+ }
94
+
95
+ @keyframes rotate {
96
+ 100% {
97
+ transform: rotate(360deg);
98
+ }
99
+ }
100
+
101
+ @keyframes dash {
102
+ 0% {
103
+ stroke-dasharray: 1, 150;
104
+ stroke-dashoffset: 0;
105
+ }
106
+ 50% {
107
+ stroke-dasharray: 90, 150;
108
+ stroke-dashoffset: -35;
109
+ }
110
+ 100% {
111
+ stroke-dasharray: 90, 150;
112
+ stroke-dashoffset: -124;
113
+ }
114
+ }
115
+
116
+ @keyframes dot_pulse {
117
+ 0%,
118
+ 80%,
119
+ 100% {
120
+ opacity: 0.3;
121
+ }
122
+ 40% {
123
+ opacity: 1;
124
+ }
125
+ }
126
+
127
+ @keyframes delayed_load {
128
+ from {
129
+ visibility: hidden;
130
+ opacity: 0;
131
+ }
132
+
133
+ to {
134
+ visibility: visible;
135
+ opacity: 1;
136
+ }
137
+ }
138
+
139
+ @keyframes skeleton_shimmer {
140
+ 0% {
141
+ background-position-x: 100%;
142
+ }
143
+ 100% {
144
+ background-position-x: -100%;
145
+ }
146
+ }
147
+
148
+ @media (prefers-reduced-motion: reduce) {
149
+ .spinner_loader .loading_spinner,
150
+ .spinner_loader .loading_path,
151
+ .dots_loader span,
152
+ .skeleton_shape,
153
+ .overlay_loader {
154
+ animation: none !important;
155
+ }
156
+ }