@musecat/uikit 0.1.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 (180) hide show
  1. package/LICENSE +9 -0
  2. package/README.md +51 -0
  3. package/index.ts +78 -0
  4. package/package.json +103 -0
  5. package/packages/Components/Box/Box.tsx +173 -0
  6. package/packages/Components/Box/Box.types.ts +48 -0
  7. package/packages/Components/Button/Button.tsx +275 -0
  8. package/packages/Components/Button/Button.types.ts +44 -0
  9. package/packages/Components/Card/Card.default.tsx +180 -0
  10. package/packages/Components/Card/Card.foldable.tsx +383 -0
  11. package/packages/Components/Card/Card.module.scss +23 -0
  12. package/packages/Components/Card/Card.tsx +8 -0
  13. package/packages/Components/Card/Card.types.ts +91 -0
  14. package/packages/Components/Card/Card.utils.ts +66 -0
  15. package/packages/Components/Checkbox/Checkbox.module.scss +11 -0
  16. package/packages/Components/Checkbox/Checkbox.tsx +104 -0
  17. package/packages/Components/Checkbox/Checkbox.types.ts +24 -0
  18. package/packages/Components/CodeBox/CodeBox.copy.tsx +43 -0
  19. package/packages/Components/CodeBox/CodeBox.module.scss +72 -0
  20. package/packages/Components/CodeBox/CodeBox.tsx +91 -0
  21. package/packages/Components/CodeBox/CodeBox.types.ts +17 -0
  22. package/packages/Components/ContextMenu/ContextMenu.module.scss +8 -0
  23. package/packages/Components/ContextMenu/ContextMenu.options.tsx +126 -0
  24. package/packages/Components/ContextMenu/ContextMenu.tsx +381 -0
  25. package/packages/Components/ContextMenu/ContextMenu.types.ts +46 -0
  26. package/packages/Components/ContributionGraph/ContributionGraph.module.scss +42 -0
  27. package/packages/Components/ContributionGraph/ContributionGraph.tsx +454 -0
  28. package/packages/Components/ContributionGraph/ContributionGraph.types.ts +27 -0
  29. package/packages/Components/DatePicker/DatePicker.calendar.tsx +161 -0
  30. package/packages/Components/DatePicker/DatePicker.core.tsx +582 -0
  31. package/packages/Components/DatePicker/DatePicker.module.scss +26 -0
  32. package/packages/Components/DatePicker/DatePicker.tsx +786 -0
  33. package/packages/Components/DatePicker/DatePicker.types.ts +36 -0
  34. package/packages/Components/DatePicker/DatePicker.utils.ts +80 -0
  35. package/packages/Components/DatePicker/hooks/useCalendar.ts +61 -0
  36. package/packages/Components/DatePicker/hooks/useSelection.ts +208 -0
  37. package/packages/Components/Divider/Divider.tsx +46 -0
  38. package/packages/Components/Divider/Divider.types.ts +12 -0
  39. package/packages/Components/Header/Header.module.scss +74 -0
  40. package/packages/Components/Header/Header.tsx +47 -0
  41. package/packages/Components/Header/Header.types.ts +8 -0
  42. package/packages/Components/Icon/Icon.group.tsx +122 -0
  43. package/packages/Components/Icon/Icon.group.types.ts +20 -0
  44. package/packages/Components/Icon/Icon.tsx +341 -0
  45. package/packages/Components/Icon/Icon.types.ts +59 -0
  46. package/packages/Components/Icon/SVG/data.tsx +232 -0
  47. package/packages/Components/Input/Input.module.scss +5 -0
  48. package/packages/Components/Input/Input.tsx +87 -0
  49. package/packages/Components/Input/Input.types.ts +14 -0
  50. package/packages/Components/Label/Label.module.scss +19 -0
  51. package/packages/Components/Label/Label.tsx +115 -0
  52. package/packages/Components/Label/Label.types.ts +28 -0
  53. package/packages/Components/Layout/Layout.docs.module.scss +42 -0
  54. package/packages/Components/Layout/Layout.docs.tsx +150 -0
  55. package/packages/Components/Layout/Layout.docs.types.ts +18 -0
  56. package/packages/Components/Layout/Layout.module.scss +91 -0
  57. package/packages/Components/Layout/Layout.tsx +306 -0
  58. package/packages/Components/Layout/Layout.types.ts +71 -0
  59. package/packages/Components/Maps/OSM/MapOSM.module.scss +11 -0
  60. package/packages/Components/Maps/OSM/MapOSM.shared.ts +121 -0
  61. package/packages/Components/Maps/OSM/MapOSM.tsx +279 -0
  62. package/packages/Components/Maps/OSM/MapOSM.types.ts +18 -0
  63. package/packages/Components/Nav/Nav.module.scss +137 -0
  64. package/packages/Components/Nav/Nav.tsx +304 -0
  65. package/packages/Components/Nav/Nav.types.ts +32 -0
  66. package/packages/Components/Nav/hooks/useNavDrag.ts +374 -0
  67. package/packages/Components/Nav/hooks/useNavIndicator.ts +137 -0
  68. package/packages/Components/Pagination/Pagination.tsx +254 -0
  69. package/packages/Components/Pagination/Pagination.types.ts +18 -0
  70. package/packages/Components/Pill/Pill.tsx +127 -0
  71. package/packages/Components/Pill/Pill.types.ts +33 -0
  72. package/packages/Components/Profile/Profile.shared.ts +74 -0
  73. package/packages/Components/Profile/Profile.tsx +382 -0
  74. package/packages/Components/Profile/Profile.types.ts +55 -0
  75. package/packages/Components/Progress/Progress.module.scss +15 -0
  76. package/packages/Components/Progress/Progress.tsx +88 -0
  77. package/packages/Components/Progress/Progress.types.ts +22 -0
  78. package/packages/Components/Radio/Radio.module.scss +11 -0
  79. package/packages/Components/Radio/Radio.tsx +121 -0
  80. package/packages/Components/Radio/Radio.types.ts +20 -0
  81. package/packages/Components/Select/Select.keyboard.tsx +76 -0
  82. package/packages/Components/Select/Select.trigger.tsx +88 -0
  83. package/packages/Components/Select/Select.tsx +11 -0
  84. package/packages/Components/Select/Select.types.ts +38 -0
  85. package/packages/Components/Select/Select.utils.ts +84 -0
  86. package/packages/Components/Select/hooks/useSelectNavigation.ts +461 -0
  87. package/packages/Components/Select/hooks/useSelectSelection.ts +142 -0
  88. package/packages/Components/Select/hooks/useSelectState.ts +314 -0
  89. package/packages/Components/Select/select.control.tsx +228 -0
  90. package/packages/Components/Select/select.inner.tsx +312 -0
  91. package/packages/Components/Skeleton/Skeleton.tsx +30 -0
  92. package/packages/Components/Skeleton/Skeleton.types.ts +12 -0
  93. package/packages/Components/Spinner/Spinner.module.scss +78 -0
  94. package/packages/Components/Spinner/Spinner.tsx +125 -0
  95. package/packages/Components/Spinner/Spinner.types.ts +19 -0
  96. package/packages/Components/Text/Text.module.scss +7 -0
  97. package/packages/Components/Text/Text.tsx +49 -0
  98. package/packages/Components/Text/Text.types.ts +33 -0
  99. package/packages/Components/TimePicker/TimePicker.core.tsx +448 -0
  100. package/packages/Components/TimePicker/TimePicker.module.scss +30 -0
  101. package/packages/Components/TimePicker/TimePicker.tsx +511 -0
  102. package/packages/Components/TimePicker/TimePicker.types.ts +22 -0
  103. package/packages/Components/Timeline/Timeline.tsx +111 -0
  104. package/packages/Components/Timeline/Timeline.types.ts +23 -0
  105. package/packages/Components/Title/Title.tsx +305 -0
  106. package/packages/Components/Title/Title.types.ts +64 -0
  107. package/packages/Components/Toggle/Toggle.module.scss +32 -0
  108. package/packages/Components/Toggle/Toggle.tsx +272 -0
  109. package/packages/Components/Toggle/Toggle.types.ts +17 -0
  110. package/packages/Components/Tooltip/Tooltip.module.scss +44 -0
  111. package/packages/Components/Tooltip/Tooltip.tsx +340 -0
  112. package/packages/Components/Tooltip/Tooltip.types.ts +38 -0
  113. package/packages/Frameworks/Dialog/Dialog.background.tsx +88 -0
  114. package/packages/Frameworks/Dialog/Dialog.boot.tsx +32 -0
  115. package/packages/Frameworks/Dialog/Dialog.store.ts +199 -0
  116. package/packages/Frameworks/Dialog/Dialog.tsx +185 -0
  117. package/packages/Frameworks/Dialog/Dialog.types.ts +264 -0
  118. package/packages/Frameworks/Dialog/Dialog.utils.ts +22 -0
  119. package/packages/Frameworks/Dialog/contents/Dialog.footer.tsx +79 -0
  120. package/packages/Frameworks/Dialog/contents/Dialog.funnel.tsx +260 -0
  121. package/packages/Frameworks/Dialog/contents/Dialog.header.tsx +137 -0
  122. package/packages/Frameworks/Dialog/hooks/useDialogPosition.ts +298 -0
  123. package/packages/Frameworks/Dialog/hooks/useSheetDrag.ts +325 -0
  124. package/packages/Frameworks/Dialog/hooks/useSheetGeometry.ts +201 -0
  125. package/packages/Frameworks/Dialog/hooks/useSheetProgressive.ts +98 -0
  126. package/packages/Frameworks/Dialog/renderers/renderModal.tsx +228 -0
  127. package/packages/Frameworks/Dialog/renderers/renderPopover.tsx +220 -0
  128. package/packages/Frameworks/Dialog/renderers/renderSheet.tsx +571 -0
  129. package/packages/Frameworks/EdgeEffect/EdgeEffect.tsx +52 -0
  130. package/packages/Frameworks/EdgeEffect/EdgeEffect.types.ts +5 -0
  131. package/packages/Frameworks/Motion/Motion.presets.ts +49 -0
  132. package/packages/Frameworks/Pressable/Pressable.module.scss +19 -0
  133. package/packages/Frameworks/Pressable/Pressable.tsx +651 -0
  134. package/packages/Frameworks/Pressable/Pressable.types.ts +73 -0
  135. package/packages/Frameworks/Squircle/Squircle.tsx +263 -0
  136. package/packages/Frameworks/Squircle/Squircle.types.ts +15 -0
  137. package/packages/Frameworks/Theme/Radius.types.ts +55 -0
  138. package/packages/Frameworks/Theme/Theme.types.ts +325 -0
  139. package/packages/Frameworks/Toaster/Toaster.boot.tsx +47 -0
  140. package/packages/Frameworks/Toaster/Toaster.module.scss +168 -0
  141. package/packages/Frameworks/Toaster/Toaster.types.ts +13 -0
  142. package/packages/Frameworks/View/DNDView/DNDView.module.scss +8 -0
  143. package/packages/Frameworks/View/DNDView/DNDView.tsx +470 -0
  144. package/packages/Frameworks/View/DNDView/DNDView.types.ts +61 -0
  145. package/packages/Frameworks/View/HScrollView/HScrollView.module.scss +59 -0
  146. package/packages/Frameworks/View/HScrollView/HScrollView.tsx +386 -0
  147. package/packages/Frameworks/View/HScrollView/HScrollView.types.ts +46 -0
  148. package/packages/Frameworks/View/ImageView/Dialog/ImageDialog.counter.tsx +27 -0
  149. package/packages/Frameworks/View/ImageView/Dialog/ImageDialog.footer.tsx +76 -0
  150. package/packages/Frameworks/View/ImageView/Dialog/ImageDialog.header.tsx +49 -0
  151. package/packages/Frameworks/View/ImageView/Dialog/ImageDialog.list.tsx +74 -0
  152. package/packages/Frameworks/View/ImageView/Dialog/ImageDialog.slide.tsx +71 -0
  153. package/packages/Frameworks/View/ImageView/Dialog/ImageDialog.tsx +175 -0
  154. package/packages/Frameworks/View/ImageView/Image.controls.tsx +39 -0
  155. package/packages/Frameworks/View/ImageView/Image.hooks.ts +82 -0
  156. package/packages/Frameworks/View/ImageView/Image.module.scss +7 -0
  157. package/packages/Frameworks/View/ImageView/Image.tsx +375 -0
  158. package/packages/Frameworks/View/ImageView/Image.types.ts +71 -0
  159. package/packages/Frameworks/View/ImageView/Image.utils.ts +79 -0
  160. package/packages/Frameworks/View/View.module.scss +34 -0
  161. package/packages/Frameworks/View/View.tsx +200 -0
  162. package/packages/Frameworks/View/View.types.ts +36 -0
  163. package/packages/Frameworks/_shared/Padding.types.ts +52 -0
  164. package/packages/Frameworks/_shared/StopParentInteraction.tsx +45 -0
  165. package/packages/Frameworks/_shared/Wind.types.ts +17 -0
  166. package/packages/Frameworks/_shared/bodyScrollLock.ts +127 -0
  167. package/packages/Frameworks/_shared/layer.constants.ts +8 -0
  168. package/packages/Frameworks/_shared/normalize.ts +39 -0
  169. package/packages/Frameworks/_shared/sizing.ts +55 -0
  170. package/packages/Frameworks/_shared/useControllableState.ts +29 -0
  171. package/packages/Styles/_animation.scss +42 -0
  172. package/packages/Styles/_color.scss +131 -0
  173. package/packages/Styles/_font.scss +30 -0
  174. package/packages/Styles/_icon.scss +782 -0
  175. package/packages/Styles/_importer.scss +9 -0
  176. package/packages/Styles/_system.scss +69 -0
  177. package/packages/Styles/_textstyle.scss +111 -0
  178. package/packages/Styles/_theme.scss +452 -0
  179. package/packages/Styles/_viewport-global.scss +99 -0
  180. package/packages/Styles/_viewport.scss +108 -0
@@ -0,0 +1,448 @@
1
+ "use client";
2
+
3
+ import clsx from "clsx";
4
+ import {
5
+ forwardRef,
6
+ type KeyboardEvent,
7
+ useCallback,
8
+ useImperativeHandle,
9
+ useMemo,
10
+ useRef,
11
+ } from "react";
12
+ import Text from "@/packages/Components/Text/Text";
13
+ import styles from "@/packages/Components/TimePicker/TimePicker.module.scss";
14
+ import Pressable from "@/packages/Frameworks/Pressable/Pressable";
15
+ import View from "@/packages/Frameworks/View/View";
16
+
17
+ type Segment = "hour" | "minute" | "second" | "ampm";
18
+
19
+ function pad(n: number): string {
20
+ return String(n).padStart(2, "0");
21
+ }
22
+
23
+ export interface TimePickerCoreRef {
24
+ focusFirst: () => void;
25
+ focusLast: () => void;
26
+ }
27
+
28
+ export interface TimePickerCoreProps {
29
+ hour: number | null;
30
+ minute: number | null;
31
+ second: number | null;
32
+ ampm: "AM" | "PM";
33
+ use12h?: boolean;
34
+ showSeconds?: boolean;
35
+ disabled?: boolean;
36
+ readOnly?: boolean;
37
+ onTimeChange: (
38
+ hour: number | null,
39
+ minute: number | null,
40
+ second: number | null,
41
+ ampm: "AM" | "PM",
42
+ ) => void;
43
+ onHourOverflow?: (info: { input: number; normalized: number }) => void;
44
+ onFocusPrev?: () => void;
45
+ onFocusNext?: () => void;
46
+ }
47
+
48
+ const TimePickerCore = forwardRef<TimePickerCoreRef, TimePickerCoreProps>(
49
+ (
50
+ {
51
+ hour,
52
+ minute,
53
+ second,
54
+ ampm,
55
+ use12h = false,
56
+ showSeconds = false,
57
+ disabled,
58
+ readOnly,
59
+ onTimeChange,
60
+ onHourOverflow,
61
+ onFocusPrev,
62
+ onFocusNext,
63
+ },
64
+ ref,
65
+ ) => {
66
+ const bufRef = useRef("");
67
+ const hourRef = useRef<HTMLInputElement>(null);
68
+ const minuteRef = useRef<HTMLInputElement>(null);
69
+ const secondRef = useRef<HTMLInputElement>(null);
70
+ const ampmRef = useRef<HTMLButtonElement>(null);
71
+
72
+ useImperativeHandle(ref, () => ({
73
+ focusFirst: () => hourRef.current?.focus(),
74
+ focusLast: () => {
75
+ if (use12h) ampmRef.current?.focus();
76
+ else if (showSeconds) secondRef.current?.focus();
77
+ else minuteRef.current?.focus();
78
+ },
79
+ }));
80
+
81
+ const order = useMemo<Segment[]>(() => {
82
+ const list: Segment[] = ["hour", "minute"];
83
+ if (showSeconds) list.push("second");
84
+ if (use12h) list.push("ampm");
85
+ return list;
86
+ }, [showSeconds, use12h]);
87
+
88
+ const getSegmentRef = useCallback((segment: Segment) => {
89
+ switch (segment) {
90
+ case "hour":
91
+ return hourRef;
92
+ case "minute":
93
+ return minuteRef;
94
+ case "second":
95
+ return secondRef;
96
+ case "ampm":
97
+ return ampmRef;
98
+ }
99
+ }, []);
100
+
101
+ const moveNext = useCallback(
102
+ (current: Segment) => {
103
+ const next = order[order.indexOf(current) + 1];
104
+ if (next) getSegmentRef(next).current?.focus();
105
+ else onFocusNext?.();
106
+ },
107
+ [getSegmentRef, order, onFocusNext],
108
+ );
109
+
110
+ const movePrev = useCallback(
111
+ (current: Segment) => {
112
+ const prev = order[order.indexOf(current) - 1];
113
+ if (prev) getSegmentRef(prev).current?.focus();
114
+ else onFocusPrev?.();
115
+ },
116
+ [getSegmentRef, order, onFocusPrev],
117
+ );
118
+
119
+ const focusAndSelect = useCallback(
120
+ (segment: Segment) => {
121
+ const target = getSegmentRef(segment).current;
122
+ if (!target) return;
123
+ requestAnimationFrame(() => {
124
+ target.focus();
125
+ if ("select" in target && typeof target.select === "function")
126
+ target.select();
127
+ });
128
+ },
129
+ [getSegmentRef],
130
+ );
131
+
132
+ const displayHour =
133
+ hour !== null
134
+ ? use12h
135
+ ? pad(hour % 12 === 0 ? 12 : hour % 12)
136
+ : pad(hour)
137
+ : "";
138
+ const displayMinute = minute !== null ? pad(minute) : "";
139
+ const displaySecond = second !== null ? pad(second) : "";
140
+
141
+ const normalizeHourInput = useCallback(
142
+ (rawValue: number) =>
143
+ use12h || rawValue < 24 ? rawValue : rawValue % 24,
144
+ [use12h],
145
+ );
146
+
147
+ const commitNumericSegment = useCallback(
148
+ (segment: "hour" | "minute" | "second", rawValue: string) => {
149
+ const digits = rawValue.replace(/\D/g, "").slice(-2);
150
+ const max = segment === "hour" ? (use12h ? 12 : 23) : 59;
151
+ const min = segment === "hour" && use12h ? 1 : 0;
152
+
153
+ let nextH = hour,
154
+ nextM = minute,
155
+ nextS = second;
156
+
157
+ if (!digits) {
158
+ const minVal = min;
159
+ if (segment === "hour") nextH = minVal;
160
+ if (segment === "minute") nextM = minVal;
161
+ if (segment === "second") nextS = minVal;
162
+ onTimeChange(nextH, nextM, nextS, ampm);
163
+ return;
164
+ }
165
+
166
+ const parsed = Number(digits);
167
+ const shouldNormalizeHour =
168
+ segment === "hour" && !use12h && parsed >= 24;
169
+ const next = shouldNormalizeHour
170
+ ? normalizeHourInput(parsed)
171
+ : Math.max(min, Math.min(max, parsed));
172
+
173
+ if (shouldNormalizeHour)
174
+ onHourOverflow?.({ input: parsed, normalized: next });
175
+
176
+ if (segment === "hour") nextH = next;
177
+ if (segment === "minute") nextM = next;
178
+ if (segment === "second") nextS = next;
179
+ onTimeChange(nextH, nextM, nextS, ampm);
180
+
181
+ if (digits.length >= 2 || parsed * 10 > max) moveNext(segment);
182
+ },
183
+ [
184
+ hour,
185
+ minute,
186
+ second,
187
+ ampm,
188
+ use12h,
189
+ moveNext,
190
+ normalizeHourInput,
191
+ onHourOverflow,
192
+ onTimeChange,
193
+ ],
194
+ );
195
+
196
+ const handleBlur = (seg: "hour" | "minute" | "second") => {
197
+ if (bufRef.current) {
198
+ const val = bufRef.current;
199
+ bufRef.current = "";
200
+ commitNumericSegment(seg, val);
201
+ }
202
+ };
203
+
204
+ const updateAmpm = useCallback(
205
+ (next: "AM" | "PM") => {
206
+ onTimeChange(hour, minute, second, next);
207
+ },
208
+ [hour, minute, second, onTimeChange],
209
+ );
210
+
211
+ const handleNumericKeyDown = (
212
+ e: KeyboardEvent<HTMLInputElement>,
213
+ segment: "hour" | "minute" | "second",
214
+ ) => {
215
+ const max = segment === "hour" ? (use12h ? 12 : 23) : 59;
216
+ const min = segment === "hour" && use12h ? 1 : 0;
217
+
218
+ let displayHourVal = hour;
219
+ if (use12h && hour !== null) {
220
+ displayHourVal = hour % 12 === 0 ? 12 : hour % 12;
221
+ }
222
+ const current =
223
+ segment === "hour"
224
+ ? displayHourVal
225
+ : segment === "minute"
226
+ ? minute
227
+ : second;
228
+ const base = current ?? min;
229
+
230
+ if (e.key === "Tab") return;
231
+
232
+ if (e.key === "Backspace") {
233
+ if (!e.currentTarget.value) {
234
+ e.preventDefault();
235
+ movePrev(segment);
236
+ }
237
+ return;
238
+ }
239
+
240
+ if (e.key === "ArrowUp") {
241
+ e.preventDefault();
242
+ const next = base + 1 > max ? min : base + 1;
243
+ let nextH = hour,
244
+ nextM = minute,
245
+ nextS = second;
246
+ if (segment === "hour") nextH = next;
247
+ if (segment === "minute") nextM = next;
248
+ if (segment === "second") nextS = next;
249
+ onTimeChange(nextH, nextM, nextS, ampm);
250
+ return;
251
+ }
252
+
253
+ if (e.key === "ArrowDown") {
254
+ e.preventDefault();
255
+ const next = base - 1 < min ? max : base - 1;
256
+ let nextH = hour,
257
+ nextM = minute,
258
+ nextS = second;
259
+ if (segment === "hour") nextH = next;
260
+ if (segment === "minute") nextM = next;
261
+ if (segment === "second") nextS = next;
262
+ onTimeChange(nextH, nextM, nextS, ampm);
263
+ return;
264
+ }
265
+
266
+ if (e.key === "ArrowRight") {
267
+ e.preventDefault();
268
+ moveNext(segment);
269
+ return;
270
+ }
271
+
272
+ if (e.key === "ArrowLeft") {
273
+ e.preventDefault();
274
+ movePrev(segment);
275
+ return;
276
+ }
277
+
278
+ if (!/^\d$/.test(e.key)) return;
279
+
280
+ e.preventDefault();
281
+ bufRef.current += e.key;
282
+ const attempt = Number(bufRef.current);
283
+
284
+ if (bufRef.current.length >= 2 || attempt * 10 > max) {
285
+ const val = bufRef.current;
286
+ bufRef.current = "";
287
+ commitNumericSegment(segment, val);
288
+ return;
289
+ }
290
+
291
+ let nextH = hour,
292
+ nextM = minute,
293
+ nextS = second;
294
+ if (segment === "hour") nextH = attempt;
295
+ if (segment === "minute") nextM = attempt;
296
+ if (segment === "second") nextS = attempt;
297
+ onTimeChange(nextH, nextM, nextS, ampm);
298
+ };
299
+
300
+ const handleAmpmKeyDown = (e: React.KeyboardEvent) => {
301
+ if (e.key === "Tab") return;
302
+
303
+ if (e.key === "ArrowLeft") {
304
+ e.preventDefault();
305
+ movePrev("ampm");
306
+ return;
307
+ }
308
+
309
+ if (e.key === "ArrowRight") {
310
+ e.preventDefault();
311
+ moveNext("ampm");
312
+ return;
313
+ }
314
+
315
+ if (
316
+ e.key === "Enter" ||
317
+ e.key === " " ||
318
+ e.key === "ArrowUp" ||
319
+ e.key === "ArrowDown"
320
+ ) {
321
+ e.preventDefault();
322
+ updateAmpm(ampm === "AM" ? "PM" : "AM");
323
+ return;
324
+ }
325
+
326
+ if (e.key.toLowerCase() === "a" || e.key.toLowerCase() === "p") {
327
+ e.preventDefault();
328
+ updateAmpm(e.key.toLowerCase() === "a" ? "AM" : "PM");
329
+ }
330
+ };
331
+
332
+ const isDisabledOrReadOnly = !!(disabled || readOnly);
333
+
334
+ return (
335
+ <>
336
+ <input
337
+ ref={hourRef}
338
+ type="text"
339
+ inputMode="numeric"
340
+ pattern="[0-9]*"
341
+ autoComplete="off"
342
+ maxLength={2}
343
+ placeholder="--"
344
+ className={clsx(styles.Field, "Subheadline")}
345
+ value={displayHour}
346
+ readOnly={isDisabledOrReadOnly}
347
+ disabled={isDisabledOrReadOnly}
348
+ onFocus={() => {
349
+ bufRef.current = "";
350
+ focusAndSelect("hour");
351
+ }}
352
+ onClick={() => focusAndSelect("hour")}
353
+ onChange={(e) => commitNumericSegment("hour", e.target.value)}
354
+ onKeyDown={(e) => handleNumericKeyDown(e, "hour")}
355
+ onBlur={() => handleBlur("hour")}
356
+ />
357
+
358
+ <Text type="Subheadline" verticalTrim className={styles.Sep}>
359
+ :
360
+ </Text>
361
+
362
+ <input
363
+ ref={minuteRef}
364
+ type="text"
365
+ inputMode="numeric"
366
+ pattern="[0-9]*"
367
+ autoComplete="off"
368
+ maxLength={2}
369
+ placeholder="--"
370
+ className={clsx(styles.Field, "Subheadline")}
371
+ value={displayMinute}
372
+ readOnly={isDisabledOrReadOnly}
373
+ disabled={isDisabledOrReadOnly}
374
+ onFocus={() => {
375
+ bufRef.current = "";
376
+ focusAndSelect("minute");
377
+ }}
378
+ onClick={() => focusAndSelect("minute")}
379
+ onChange={(e) => commitNumericSegment("minute", e.target.value)}
380
+ onKeyDown={(e) => handleNumericKeyDown(e, "minute")}
381
+ onBlur={() => handleBlur("minute")}
382
+ />
383
+
384
+ {showSeconds && (
385
+ <>
386
+ <Text type="Subheadline" verticalTrim className={styles.Sep}>
387
+ :
388
+ </Text>
389
+ <input
390
+ ref={secondRef}
391
+ type="text"
392
+ inputMode="numeric"
393
+ pattern="[0-9]*"
394
+ autoComplete="off"
395
+ maxLength={2}
396
+ placeholder="--"
397
+ className={clsx(styles.Field, "Subheadline")}
398
+ value={displaySecond}
399
+ readOnly={isDisabledOrReadOnly}
400
+ disabled={isDisabledOrReadOnly}
401
+ onFocus={() => {
402
+ bufRef.current = "";
403
+ focusAndSelect("second");
404
+ }}
405
+ onClick={() => focusAndSelect("second")}
406
+ onChange={(e) => commitNumericSegment("second", e.target.value)}
407
+ onKeyDown={(e) => handleNumericKeyDown(e, "second")}
408
+ onBlur={() => handleBlur("second")}
409
+ />
410
+ </>
411
+ )}
412
+
413
+ {use12h && (
414
+ <>
415
+ <Text type="Subheadline" verticalTrim className={styles.Sep}>
416
+ &nbsp;
417
+ </Text>
418
+ <View className={styles.AmpmWrap}>
419
+ <Pressable
420
+ ref={ampmRef}
421
+ className={clsx(
422
+ styles.Field,
423
+ styles.FieldAmpm,
424
+ styles.FieldButton,
425
+ )}
426
+ disabled={isDisabledOrReadOnly}
427
+ onFocus={() => {
428
+ bufRef.current = "";
429
+ }}
430
+ onClick={() => {
431
+ if (isDisabledOrReadOnly) return;
432
+ updateAmpm(ampm === "AM" ? "PM" : "AM");
433
+ }}
434
+ onKeyDown={handleAmpmKeyDown}
435
+ >
436
+ <Text type="Subheadline" verticalTrim>
437
+ {ampm}
438
+ </Text>
439
+ </Pressable>
440
+ </View>
441
+ </>
442
+ )}
443
+ </>
444
+ );
445
+ },
446
+ );
447
+
448
+ export default TimePickerCore;
@@ -0,0 +1,30 @@
1
+ .Field {
2
+ background: transparent;
3
+ border: 0;
4
+ outline: 0 !important;
5
+ text-align: center;
6
+ width: 2rem;
7
+ padding: 0;
8
+ caret-color: transparent;
9
+ cursor: default;
10
+
11
+ &:focus {
12
+ background: var(--color-base-3);
13
+ }
14
+ }
15
+
16
+ .FieldButton {
17
+ appearance: none;
18
+ }
19
+
20
+ .FieldAmpm {
21
+ width: 2.6rem;
22
+ transition: var(--transition-color);
23
+ }
24
+
25
+ .Sep {
26
+ opacity: 0.4;
27
+ user-select: none;
28
+ pointer-events: none;
29
+ margin-top: -0.15rem;
30
+ }