@sixthshift/design-system 0.1.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 (236) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +222 -0
  3. package/dist/styles.css +2 -0
  4. package/dist/theme.generated.css +438 -0
  5. package/package.json +238 -0
  6. package/src/components/Avatar/Avatar.tsx +19 -0
  7. package/src/components/Avatar/index.ts +1 -0
  8. package/src/components/Badge/Badge.tsx +59 -0
  9. package/src/components/Badge/index.ts +1 -0
  10. package/src/components/BarChart/BarChart.tsx +51 -0
  11. package/src/components/BarChart/index.ts +1 -0
  12. package/src/components/Breadcrumb/Breadcrumb.tsx +47 -0
  13. package/src/components/Breadcrumb/index.ts +5 -0
  14. package/src/components/Button/Button.tsx +157 -0
  15. package/src/components/Button/index.ts +1 -0
  16. package/src/components/Calendar/Calendar.tsx +433 -0
  17. package/src/components/Calendar/calendar.hooks.ts +329 -0
  18. package/src/components/Calendar/calendar.types.ts +117 -0
  19. package/src/components/Calendar/index.ts +28 -0
  20. package/src/components/Card/Card.tsx +47 -0
  21. package/src/components/Card/index.ts +1 -0
  22. package/src/components/Checkbox/Checkbox.tsx +144 -0
  23. package/src/components/Checkbox/index.ts +1 -0
  24. package/src/components/CheckboxGroup/CheckboxGroup.tsx +173 -0
  25. package/src/components/CheckboxGroup/index.ts +5 -0
  26. package/src/components/Code/Editor/Editor.tsx +161 -0
  27. package/src/components/Code/Editor/index.ts +2 -0
  28. package/src/components/Code/Editor/semanticTokenProvider.ts +69 -0
  29. package/src/components/Code/Editor/themes.ts +112 -0
  30. package/src/components/Code/ValidationStatus/ValidationStatus.tsx +81 -0
  31. package/src/components/Code/ValidationStatus/index.ts +1 -0
  32. package/src/components/Code/Workspace/StatusBar.tsx +47 -0
  33. package/src/components/Code/Workspace/Toolbar.tsx +47 -0
  34. package/src/components/Code/Workspace/Workspace.tsx +109 -0
  35. package/src/components/Code/Workspace/index.ts +3 -0
  36. package/src/components/ColorDot/ColorDot.tsx +50 -0
  37. package/src/components/ColorDot/index.ts +1 -0
  38. package/src/components/DatePicker/DatePicker.tsx +236 -0
  39. package/src/components/DatePicker/datepicker.hooks.ts +381 -0
  40. package/src/components/DatePicker/datepicker.types.ts +140 -0
  41. package/src/components/DatePicker/index.ts +12 -0
  42. package/src/components/DateRangePicker/DateRangePicker.tsx +127 -0
  43. package/src/components/DateRangePicker/daterangepicker.types.ts +53 -0
  44. package/src/components/DateRangePicker/index.ts +2 -0
  45. package/src/components/DateTimePicker/DateTimePicker.tsx +399 -0
  46. package/src/components/DateTimePicker/datetimepicker.types.ts +48 -0
  47. package/src/components/DateTimePicker/index.ts +2 -0
  48. package/src/components/DateTimeRangePicker/DateTimeRangePicker.tsx +679 -0
  49. package/src/components/DateTimeRangePicker/datetimerangepicker.types.ts +62 -0
  50. package/src/components/DateTimeRangePicker/index.ts +2 -0
  51. package/src/components/EmptyState/EmptyState.tsx +29 -0
  52. package/src/components/EmptyState/index.ts +1 -0
  53. package/src/components/Field/Field.tsx +60 -0
  54. package/src/components/Field/index.ts +1 -0
  55. package/src/components/FormField/FormField.tsx +55 -0
  56. package/src/components/FormField/index.ts +2 -0
  57. package/src/components/HeatMap/HeatMapCalendar.tsx +127 -0
  58. package/src/components/HeatMap/HeatMapMatrix.tsx +75 -0
  59. package/src/components/HeatMap/heat-map.utils.ts +15 -0
  60. package/src/components/HeatMap/index.ts +3 -0
  61. package/src/components/HoverCard/HoverCard.tsx +83 -0
  62. package/src/components/HoverCard/components/HoverCardContent.tsx +30 -0
  63. package/src/components/HoverCard/components/HoverCardContext.tsx +22 -0
  64. package/src/components/HoverCard/components/HoverCardTrigger.tsx +32 -0
  65. package/src/components/HoverCard/index.ts +3 -0
  66. package/src/components/Input/Input.tsx +48 -0
  67. package/src/components/Input/index.ts +1 -0
  68. package/src/components/LineChart/LineChart.tsx +251 -0
  69. package/src/components/LineChart/index.ts +1 -0
  70. package/src/components/Markdown/Markdown.tsx +40 -0
  71. package/src/components/Markdown/index.ts +1 -0
  72. package/src/components/Message/Message.tsx +59 -0
  73. package/src/components/Message/components/MessageBody.tsx +9 -0
  74. package/src/components/Message/components/MessageDescription.tsx +9 -0
  75. package/src/components/Message/components/MessageIcon.tsx +13 -0
  76. package/src/components/Message/components/MessageTitle.tsx +9 -0
  77. package/src/components/Message/index.ts +10 -0
  78. package/src/components/MetricList/MetricList.tsx +13 -0
  79. package/src/components/MetricList/MetricRow.tsx +30 -0
  80. package/src/components/MetricList/index.ts +2 -0
  81. package/src/components/Modal/Modal.tsx +146 -0
  82. package/src/components/Modal/components/ModalBody.tsx +9 -0
  83. package/src/components/Modal/components/ModalContext.tsx +27 -0
  84. package/src/components/Modal/components/ModalFooter.tsx +9 -0
  85. package/src/components/Modal/components/ModalHeader.tsx +29 -0
  86. package/src/components/Modal/components/index.ts +9 -0
  87. package/src/components/Modal/index.ts +13 -0
  88. package/src/components/NavBottom/NavBottom.tsx +53 -0
  89. package/src/components/NavBottom/index.ts +3 -0
  90. package/src/components/NavSide/NavItem.tsx +39 -0
  91. package/src/components/NavSide/NavSide.tsx +40 -0
  92. package/src/components/NavSide/index.ts +2 -0
  93. package/src/components/NavSide/types.ts +36 -0
  94. package/src/components/Pagination/Pagination.tsx +86 -0
  95. package/src/components/Pagination/index.ts +2 -0
  96. package/src/components/Popover/Popover.tsx +68 -0
  97. package/src/components/Popover/components/PopoverBody.tsx +32 -0
  98. package/src/components/Popover/components/PopoverClose.tsx +26 -0
  99. package/src/components/Popover/components/PopoverContext.tsx +24 -0
  100. package/src/components/Popover/components/PopoverTrigger.tsx +31 -0
  101. package/src/components/Popover/index.ts +10 -0
  102. package/src/components/ProgressBar/ProgressBar.tsx +42 -0
  103. package/src/components/ProgressBar/index.ts +1 -0
  104. package/src/components/RadioButton/RadioButton.tsx +89 -0
  105. package/src/components/RadioButton/index.ts +1 -0
  106. package/src/components/RadioButtonGroup/RadioButtonGroup.tsx +193 -0
  107. package/src/components/RadioButtonGroup/index.ts +5 -0
  108. package/src/components/SearchInput/SearchInput.tsx +45 -0
  109. package/src/components/SearchInput/index.ts +2 -0
  110. package/src/components/Select/Select.tsx +289 -0
  111. package/src/components/Select/SelectDropdown.tsx +90 -0
  112. package/src/components/Select/SelectTrigger.tsx +130 -0
  113. package/src/components/Select/index.ts +3 -0
  114. package/src/components/Select/useSelectKeyboard.ts +111 -0
  115. package/src/components/Separator/Separator.tsx +20 -0
  116. package/src/components/Separator/index.ts +1 -0
  117. package/src/components/Sheet/Sheet.tsx +136 -0
  118. package/src/components/Sheet/components/SheetBody.tsx +9 -0
  119. package/src/components/Sheet/components/SheetContext.tsx +23 -0
  120. package/src/components/Sheet/components/SheetFooter.tsx +9 -0
  121. package/src/components/Sheet/components/SheetHeader.tsx +29 -0
  122. package/src/components/Sheet/components/index.ts +9 -0
  123. package/src/components/Sheet/index.ts +13 -0
  124. package/src/components/Skeleton/Skeleton.tsx +7 -0
  125. package/src/components/Skeleton/index.ts +1 -0
  126. package/src/components/Sparkline/Sparkline.tsx +93 -0
  127. package/src/components/Sparkline/index.ts +1 -0
  128. package/src/components/Spinner/Spinner.tsx +41 -0
  129. package/src/components/Spinner/index.ts +1 -0
  130. package/src/components/StatsCard/StatsCard.tsx +40 -0
  131. package/src/components/StatsCard/index.ts +1 -0
  132. package/src/components/Switch/Switch.tsx +128 -0
  133. package/src/components/Switch/index.ts +1 -0
  134. package/src/components/Tabs/Tabs.tsx +49 -0
  135. package/src/components/Tabs/components/TabsContext.tsx +35 -0
  136. package/src/components/Tabs/components/TabsList.tsx +98 -0
  137. package/src/components/Tabs/components/TabsPanels.tsx +26 -0
  138. package/src/components/Tabs/components/TabsTrigger.tsx +69 -0
  139. package/src/components/Tabs/index.ts +3 -0
  140. package/src/components/TagChip/TagChip.tsx +46 -0
  141. package/src/components/TagChip/index.ts +1 -0
  142. package/src/components/TagInput/TagInput.tsx +60 -0
  143. package/src/components/TagInput/index.ts +1 -0
  144. package/src/components/Text/Text.tsx +35 -0
  145. package/src/components/Text/index.ts +1 -0
  146. package/src/components/TextInline/TextInline.tsx +35 -0
  147. package/src/components/TextInline/index.ts +1 -0
  148. package/src/components/Textarea/Textarea.tsx +20 -0
  149. package/src/components/Textarea/index.ts +1 -0
  150. package/src/components/TimePicker/PeriodSelector.tsx +28 -0
  151. package/src/components/TimePicker/TimeColumn.tsx +62 -0
  152. package/src/components/TimePicker/TimePicker.tsx +323 -0
  153. package/src/components/TimePicker/index.ts +7 -0
  154. package/src/components/TimePicker/timepicker.hooks.ts +173 -0
  155. package/src/components/TimePicker/timepicker.types.ts +101 -0
  156. package/src/components/Toast/Toast.tsx +111 -0
  157. package/src/components/Toast/index.ts +1 -0
  158. package/src/components/Toggle/Toggle.tsx +72 -0
  159. package/src/components/Toggle/index.ts +1 -0
  160. package/src/components/ToggleGroup/ToggleGroup.tsx +14 -0
  161. package/src/components/ToggleGroup/ToggleGroupContainer.tsx +19 -0
  162. package/src/components/ToggleGroup/ToggleGroupItem.tsx +51 -0
  163. package/src/components/ToggleGroup/ToggleGroupMultiple.tsx +61 -0
  164. package/src/components/ToggleGroup/ToggleGroupSingle.tsx +105 -0
  165. package/src/components/ToggleGroup/index.ts +2 -0
  166. package/src/components/ToggleGroup/toggleGroup.types.ts +55 -0
  167. package/src/components/Tooltip/Tooltip.tsx +66 -0
  168. package/src/components/Tooltip/components/TooltipBody.tsx +30 -0
  169. package/src/components/Tooltip/components/TooltipContext.tsx +22 -0
  170. package/src/components/Tooltip/components/TooltipTrigger.tsx +27 -0
  171. package/src/components/Tooltip/index.ts +9 -0
  172. package/src/date-time/arithmetic.ts +101 -0
  173. package/src/date-time/boundaries.ts +91 -0
  174. package/src/date-time/compare.ts +85 -0
  175. package/src/date-time/duration.ts +63 -0
  176. package/src/date-time/elapsed.ts +18 -0
  177. package/src/date-time/format.ts +722 -0
  178. package/src/date-time/index.ts +279 -0
  179. package/src/date-time/now.ts +70 -0
  180. package/src/date-time/parse.ts +170 -0
  181. package/src/date-time/serialize.ts +90 -0
  182. package/src/date-time/timezone.ts +47 -0
  183. package/src/date-time/validate.ts +278 -0
  184. package/src/hooks/index.ts +10 -0
  185. package/src/hooks/useCollapsible.ts +16 -0
  186. package/src/hooks/useControllableState.ts +54 -0
  187. package/src/hooks/useDebouncedCallback.ts +21 -0
  188. package/src/hooks/useDebouncedState.ts +20 -0
  189. package/src/hooks/useDeviceFormFactor.ts +49 -0
  190. package/src/hooks/useLocalStorage.ts +67 -0
  191. package/src/hooks/useMergedFloatingRef.ts +29 -0
  192. package/src/hooks/usePresence.ts +127 -0
  193. package/src/hooks/useStack.ts +99 -0
  194. package/src/hooks/useTheme.ts +105 -0
  195. package/src/internal/Slot.tsx +79 -0
  196. package/src/internal/index.ts +2 -0
  197. package/src/internal/rovingFocus.ts +39 -0
  198. package/src/internal/types.ts +10 -0
  199. package/src/lib/EmptyBoundary.tsx +12 -0
  200. package/src/lib/ErrorBoundary.tsx +48 -0
  201. package/src/lib/contexts/ComponentsContext/ComponentsContext.tsx +34 -0
  202. package/src/lib/contexts/ComponentsContext/index.ts +1 -0
  203. package/src/lib/contexts/OverlayContext/OverlayContext.tsx +93 -0
  204. package/src/lib/contexts/OverlayContext/hooks/useModal.tsx +125 -0
  205. package/src/lib/contexts/OverlayContext/hooks/useToast.ts +80 -0
  206. package/src/lib/contexts/OverlayContext/index.ts +6 -0
  207. package/src/lib/format.ts +51 -0
  208. package/src/lib/utils.ts +10 -0
  209. package/src/lib/withEmpty.tsx +14 -0
  210. package/src/lib/withErrorBoundary.tsx +16 -0
  211. package/src/lib/withSuspense.tsx +16 -0
  212. package/src/lib/withSuspenseAndErrorBoundary.tsx +26 -0
  213. package/src/styles/base.css +41 -0
  214. package/src/theme/index.ts +37 -0
  215. package/src/theme/palette.json +100 -0
  216. package/src/theme/palette.schema.json +40 -0
  217. package/src/theme/schema.ts +122 -0
  218. package/src/theme/theme.json +349 -0
  219. package/src/theme/theme.schema.json +876 -0
  220. package/src/theme/typography.json +7 -0
  221. package/src/theme/typography.schema.json +36 -0
  222. package/src/typography/Body.tsx +8 -0
  223. package/src/typography/Caption.tsx +8 -0
  224. package/src/typography/Code.tsx +8 -0
  225. package/src/typography/Display.tsx +8 -0
  226. package/src/typography/Emphasis.tsx +8 -0
  227. package/src/typography/Heading.tsx +23 -0
  228. package/src/typography/Label.tsx +10 -0
  229. package/src/typography/Lead.tsx +8 -0
  230. package/src/typography/Mono.tsx +8 -0
  231. package/src/typography/Muted.tsx +8 -0
  232. package/src/typography/SectionTitle.tsx +8 -0
  233. package/src/typography/Subtitle.tsx +8 -0
  234. package/src/typography/Timestamp.tsx +8 -0
  235. package/src/typography/index.ts +10 -0
  236. package/tailwind.config.ts +261 -0
@@ -0,0 +1,279 @@
1
+ /**
2
+ * @sixthshift/design-system/date-time - Date/time utilities using Temporal API
3
+ *
4
+ * This package provides:
5
+ * - Re-export of Temporal from @js-temporal/polyfill
6
+ * - Parsing utilities (ISO string → Temporal)
7
+ * - Serialization utilities (Temporal → ISO strings for APIs/storage)
8
+ * - Formatting utilities (Temporal → human-readable strings for UI)
9
+ * - Common utility functions
10
+ *
11
+ * When native Temporal lands in browsers/Node, update the Temporal export
12
+ * in this file and all consuming packages get native support automatically.
13
+ *
14
+ * @example
15
+ * import { Temporal, parseDate, serialize, formatDateShort, isToday } from '@sixthshift/design-system/date-time';
16
+ *
17
+ * // Parse ISO string from API
18
+ * const dueDate = parseDate('2025-01-15');
19
+ *
20
+ * // Work with Temporal
21
+ * const tomorrow = dueDate.add({ days: 1 });
22
+ * const overdue = isToday(dueDate);
23
+ *
24
+ * // Serialize back to ISO for API
25
+ * const isoString = serialize(tomorrow); // '2025-01-16'
26
+ *
27
+ * // Format for UI display
28
+ * const display = formatDateShort(tomorrow); // 'Jan 16'
29
+ */
30
+
31
+ // =============================================================================
32
+ // Temporal API (re-export from polyfill)
33
+ // =============================================================================
34
+ // When native Temporal lands, change this to:
35
+ // export const { Temporal } = globalThis;
36
+
37
+ // Re-export the Temporal namespace type for convenience
38
+ export type { Temporal as TemporalNamespace } from "@js-temporal/polyfill";
39
+ export { Temporal } from "@js-temporal/polyfill";
40
+
41
+ // =============================================================================
42
+ // Parsing: ISO 8601 string → Temporal
43
+ // =============================================================================
44
+
45
+ export {
46
+ fromDate,
47
+ parseDate,
48
+ parseDateOrUndefined,
49
+ parseDateTime,
50
+ parseDateTimeOrUndefined,
51
+ parseDuration,
52
+ parseDurationOrUndefined,
53
+ parseInstant,
54
+ parseInstantOrUndefined,
55
+ parseTime,
56
+ parseTimeOrUndefined,
57
+ parseZoned,
58
+ parseZonedOrUndefined,
59
+ } from "./parse";
60
+
61
+ // =============================================================================
62
+ // Formatting: Temporal → strings
63
+ // =============================================================================
64
+
65
+ export {
66
+ // Pattern-based formatting
67
+ formatDate,
68
+ formatDateFull,
69
+ formatDateLong,
70
+ formatDateMedium,
71
+ formatDateMediumYear,
72
+ formatDateNumeric,
73
+ formatDateNumericYear,
74
+ // Date range formats
75
+ formatDateRange,
76
+ // Date formats
77
+ formatDateShort,
78
+ formatDateShortWeekday,
79
+ formatDateShortWeekdayYear,
80
+ formatDateShortYear,
81
+ // Composite type formats
82
+ formatDateTimeTime,
83
+ formatDurationShort,
84
+ formatInstantDateTime,
85
+ formatInstantShortYear,
86
+ formatInstantTime,
87
+ // Duration formats
88
+ formatMilliseconds,
89
+ formatMonthYear,
90
+ formatMonthYearShort,
91
+ // Compact recency (1d/60d/2mo/1y) for index rows
92
+ formatRecency,
93
+ // Relative formats
94
+ formatRelative,
95
+ formatRelativeInstant,
96
+ formatRelativeToNow,
97
+ // Time formats
98
+ formatTime,
99
+ formatTime24,
100
+ formatTimeCompact,
101
+ formatTimeHour,
102
+ formatTimePadded,
103
+ formatWeekRange,
104
+ formatZonedTime,
105
+ } from "./format";
106
+
107
+ // =============================================================================
108
+ // Validation
109
+ // =============================================================================
110
+
111
+ export type { ValidationResult } from "./validate";
112
+ export {
113
+ isDuration,
114
+ isInstant,
115
+ // String validation
116
+ isISODate,
117
+ isISODateTime,
118
+ isISODuration,
119
+ isISOInstant,
120
+ isISOString,
121
+ isISOTime,
122
+ isISOZoned,
123
+ // Type guards
124
+ isPlainDate,
125
+ isPlainDateTime,
126
+ isPlainTime,
127
+ isTemporal,
128
+ isZonedDateTime,
129
+ // Validation with errors
130
+ validateISODate,
131
+ validateISODuration,
132
+ validateISOInstant,
133
+ validateISOZoned,
134
+ } from "./validate";
135
+
136
+ // =============================================================================
137
+ // Current time
138
+ // =============================================================================
139
+
140
+ export {
141
+ currentTime,
142
+ now,
143
+ nowDateTime,
144
+ nowDateTimeISO,
145
+ nowInTimezone,
146
+ nowISO,
147
+ nowZoned,
148
+ today,
149
+ todayISO,
150
+ } from "./now";
151
+
152
+ // =============================================================================
153
+ // Comparison
154
+ // =============================================================================
155
+
156
+ export {
157
+ isAfter,
158
+ isBefore,
159
+ isFuture,
160
+ isInstantAfter,
161
+ isInstantBefore,
162
+ isInstantFuture,
163
+ isInstantPast,
164
+ isPast,
165
+ isSameDay,
166
+ isSameMonth,
167
+ isToday,
168
+ } from "./compare";
169
+
170
+ // =============================================================================
171
+ // Arithmetic
172
+ // =============================================================================
173
+
174
+ export {
175
+ addDays,
176
+ addMonths,
177
+ addWeeks,
178
+ differenceInMinutes,
179
+ diffInDays,
180
+ diffInMonths,
181
+ eachDayOfInterval,
182
+ subtractDays,
183
+ subtractMonths,
184
+ subtractWeeks,
185
+ } from "./arithmetic";
186
+
187
+ // =============================================================================
188
+ // Boundaries
189
+ // =============================================================================
190
+
191
+ export type { WeekStartsOn } from "./boundaries";
192
+ export {
193
+ endOfDay,
194
+ endOfMonth,
195
+ endOfWeek,
196
+ endOfYear,
197
+ startOfDay,
198
+ startOfMonth,
199
+ startOfWeek,
200
+ startOfYear,
201
+ } from "./boundaries";
202
+
203
+ // =============================================================================
204
+ // Serialization: Temporal → ISO 8601 strings
205
+ // =============================================================================
206
+
207
+ export {
208
+ serialize,
209
+ serializeDate,
210
+ serializeDateTime,
211
+ serializeDuration,
212
+ serializeInstant,
213
+ serializeOptional,
214
+ serializeTime,
215
+ serializeZonedDateTime,
216
+ } from "./serialize";
217
+
218
+ // =============================================================================
219
+ // Timezone
220
+ // =============================================================================
221
+
222
+ export {
223
+ changeTimezone,
224
+ plainDateTimeToZonedDateTime,
225
+ plainDateToZonedDateTime,
226
+ toTimezone,
227
+ } from "./timezone";
228
+
229
+ // =============================================================================
230
+ // API Boundary Pattern
231
+ // =============================================================================
232
+ //
233
+ // When sending Temporal types to the API, use these patterns:
234
+ //
235
+ // For Instant (timezone-aware absolute time) values:
236
+ // serialize(instant)
237
+ // → "2026-01-24T23:30:00Z"
238
+ //
239
+ // For PlainDate (date-only, timezone-agnostic) values:
240
+ // serialize(plainDateToZonedDateTime(date))
241
+ // → "2026-01-24T00:00:00-08:00[America/Los_Angeles]"
242
+ //
243
+ // Why?
244
+ // - Instant is already absolute UTC time - serialize directly
245
+ // - PlainDate needs timezone context - interpret in user's timezone, then serialize
246
+ // - Server parseInstant() handles both formats correctly
247
+ //
248
+ // PREFER Instant for all datetime values in UI components and API hooks.
249
+ // PlainDate is only for date-only values (birthdays, all-day events).
250
+ //
251
+ // Example in API hook (CURRENT PATTERN):
252
+ // const params = {
253
+ // since: dateRange?.from ? serialize(dateRange.from) : undefined, // dateRange uses Instant
254
+ // until: dateRange?.to ? serialize(dateRange.to) : undefined,
255
+ // };
256
+ //
257
+ // See: packages/web/src/api/activity-log/useActivityLog.ts for reference implementation
258
+ // =============================================================================
259
+
260
+ // =============================================================================
261
+ // Duration
262
+ // =============================================================================
263
+
264
+ export {
265
+ days,
266
+ hours,
267
+ minutes,
268
+ toHours,
269
+ toMilliseconds,
270
+ toMinutes,
271
+ toSeconds,
272
+ weeks,
273
+ } from "./duration";
274
+
275
+ // =============================================================================
276
+ // Elapsed time
277
+ // =============================================================================
278
+
279
+ export { elapsedMs, epochMs } from "./elapsed";
@@ -0,0 +1,70 @@
1
+ /**
2
+ * Current time/date utilities
3
+ *
4
+ * Functions that answer "what time/date is it right now?"
5
+ */
6
+
7
+ import { Temporal } from "@js-temporal/polyfill";
8
+
9
+ /**
10
+ * Get the current instant (point in time)
11
+ */
12
+ export function now(): Temporal.Instant {
13
+ return Temporal.Now.instant();
14
+ }
15
+
16
+ /**
17
+ * Get the current instant as an ISO 8601 string
18
+ */
19
+ export function nowISO(): string {
20
+ return Temporal.Now.instant().toString();
21
+ }
22
+
23
+ /**
24
+ * Get today's date in the system timezone
25
+ */
26
+ export function today(): Temporal.PlainDate {
27
+ return Temporal.Now.plainDateISO();
28
+ }
29
+
30
+ /**
31
+ * Get today's date as an ISO 8601 string
32
+ */
33
+ export function todayISO(): string {
34
+ return Temporal.Now.plainDateISO().toString();
35
+ }
36
+
37
+ /**
38
+ * Get the current time in the system timezone
39
+ */
40
+ export function currentTime(): Temporal.PlainTime {
41
+ return Temporal.Now.plainTimeISO();
42
+ }
43
+
44
+ /**
45
+ * Get the current date/time in the system timezone (without timezone info)
46
+ */
47
+ export function nowDateTime(): Temporal.PlainDateTime {
48
+ return Temporal.Now.plainDateTimeISO();
49
+ }
50
+
51
+ /**
52
+ * Get the current date/time as an ISO 8601 string (without timezone info)
53
+ */
54
+ export function nowDateTimeISO(): string {
55
+ return Temporal.Now.plainDateTimeISO().toString();
56
+ }
57
+
58
+ /**
59
+ * Get the current zoned date/time in the system timezone
60
+ */
61
+ export function nowZoned(): Temporal.ZonedDateTime {
62
+ return Temporal.Now.zonedDateTimeISO();
63
+ }
64
+
65
+ /**
66
+ * Get the current zoned date/time in a specific timezone
67
+ */
68
+ export function nowInTimezone(timezone: string): Temporal.ZonedDateTime {
69
+ return Temporal.Now.zonedDateTimeISO(timezone);
70
+ }
@@ -0,0 +1,170 @@
1
+ /**
2
+ * Parsing utilities: External data → Temporal
3
+ *
4
+ * Use these at integration boundaries when receiving data from external sources:
5
+ * - ISO 8601 strings from APIs
6
+ * - Date objects from Node.js APIs or third-party libraries
7
+ */
8
+
9
+ import { Temporal } from "@js-temporal/polyfill";
10
+
11
+ /**
12
+ * Parse an ISO 8601 instant string to Temporal.Instant
13
+ *
14
+ * @example
15
+ * parseInstant('2025-01-15T14:30:00.000Z')
16
+ * // → Temporal.Instant
17
+ */
18
+ export function parseInstant(iso: string): Temporal.Instant {
19
+ return Temporal.Instant.from(iso);
20
+ }
21
+
22
+ /**
23
+ * Parse an ISO 8601 instant string to Temporal.Instant, or undefined if input is nullish
24
+ */
25
+ export function parseInstantOrUndefined(iso: string | null | undefined): Temporal.Instant | undefined {
26
+ return iso ? Temporal.Instant.from(iso) : undefined;
27
+ }
28
+
29
+ /**
30
+ * Parse an ISO 8601 date string to Temporal.PlainDate
31
+ *
32
+ * Accepts formats:
33
+ * - '2025-01-15' (date only)
34
+ * - '2025-01-15T14:30:00' (datetime, time portion ignored)
35
+ * - '2025-01-15T14:30:00Z' (datetime with Z, time portion ignored)
36
+ * - '2025-01-15T14:30:00.000Z' (datetime with milliseconds and Z)
37
+ *
38
+ * @example
39
+ * parseDate('2025-01-15')
40
+ * // → Temporal.PlainDate
41
+ */
42
+ export function parseDate(iso: string): Temporal.PlainDate {
43
+ // Extract just the date portion (first 10 characters: YYYY-MM-DD)
44
+ const datePart = iso.slice(0, 10);
45
+ return Temporal.PlainDate.from(datePart);
46
+ }
47
+
48
+ /**
49
+ * Parse an ISO 8601 date string to Temporal.PlainDate, or undefined if input is nullish
50
+ *
51
+ * Accepts same formats as parseDate.
52
+ */
53
+ export function parseDateOrUndefined(iso: string | null | undefined): Temporal.PlainDate | undefined {
54
+ if (!iso) return undefined;
55
+ const datePart = iso.slice(0, 10);
56
+ return Temporal.PlainDate.from(datePart);
57
+ }
58
+
59
+ /**
60
+ * Parse an ISO 8601 time string to Temporal.PlainTime
61
+ *
62
+ * @example
63
+ * parseTime('14:30:00')
64
+ * // → Temporal.PlainTime
65
+ */
66
+ export function parseTime(iso: string): Temporal.PlainTime {
67
+ return Temporal.PlainTime.from(iso);
68
+ }
69
+
70
+ /**
71
+ * Parse an ISO 8601 time string to Temporal.PlainTime, or undefined if input is nullish
72
+ */
73
+ export function parseTimeOrUndefined(iso: string | null | undefined): Temporal.PlainTime | undefined {
74
+ return iso ? Temporal.PlainTime.from(iso) : undefined;
75
+ }
76
+
77
+ /**
78
+ * Parse an ISO 8601 datetime string to Temporal.PlainDateTime
79
+ *
80
+ * Accepts formats:
81
+ * - '2025-01-15T14:30:00' (datetime)
82
+ * - '2025-01-15T14:30:00Z' (datetime with Z, Z is stripped)
83
+ * - '2025-01-15T14:30:00.000Z' (datetime with milliseconds and Z)
84
+ *
85
+ * @example
86
+ * parseDateTime('2025-01-15T14:30:00')
87
+ * // → Temporal.PlainDateTime
88
+ */
89
+ export function parseDateTime(iso: string): Temporal.PlainDateTime {
90
+ // Strip trailing Z if present (PlainDateTime doesn't accept timezone designators)
91
+ const normalized = iso.endsWith("Z") ? iso.slice(0, -1) : iso;
92
+ return Temporal.PlainDateTime.from(normalized);
93
+ }
94
+
95
+ /**
96
+ * Parse an ISO 8601 datetime string to Temporal.PlainDateTime, or undefined if input is nullish
97
+ *
98
+ * Accepts same formats as parseDateTime.
99
+ */
100
+ export function parseDateTimeOrUndefined(iso: string | null | undefined): Temporal.PlainDateTime | undefined {
101
+ if (!iso) return undefined;
102
+ const normalized = iso.endsWith("Z") ? iso.slice(0, -1) : iso;
103
+ return Temporal.PlainDateTime.from(normalized);
104
+ }
105
+
106
+ /**
107
+ * Parse an ISO 8601 zoned datetime string to Temporal.ZonedDateTime
108
+ *
109
+ * Accepts formats:
110
+ * - '2025-01-15T14:30:00[America/New_York]'
111
+ * - '2025-01-15T14:30:00-05:00[America/New_York]'
112
+ *
113
+ * @example
114
+ * parseZoned('2025-01-15T14:30:00[America/New_York]')
115
+ * // → Temporal.ZonedDateTime
116
+ */
117
+ export function parseZoned(iso: string): Temporal.ZonedDateTime {
118
+ return Temporal.ZonedDateTime.from(iso);
119
+ }
120
+
121
+ /**
122
+ * Parse an ISO 8601 zoned datetime string to Temporal.ZonedDateTime, or undefined if input is nullish
123
+ */
124
+ export function parseZonedOrUndefined(iso: string | null | undefined): Temporal.ZonedDateTime | undefined {
125
+ return iso ? Temporal.ZonedDateTime.from(iso) : undefined;
126
+ }
127
+
128
+ // =============================================================================
129
+ // Date object conversion
130
+ // =============================================================================
131
+
132
+ /**
133
+ * Convert a JavaScript Date object to Temporal.Instant
134
+ *
135
+ * Use this at API boundaries where external libraries return Date objects
136
+ * (e.g., Node.js fs.Stats.mtime, pg driver timestamptz columns, pg-boss next_run_on)
137
+ *
138
+ * @example
139
+ * // Converting fs.Stats mtime
140
+ * const instant = fromDate(stats.mtime);
141
+ *
142
+ * // Converting pg driver Date
143
+ * const instant = fromDate(pgResult.created_at);
144
+ */
145
+ // biome-ignore lint/style/noRestrictedGlobals: Date parameter required for converting from external APIs
146
+ export function fromDate(date: Date): Temporal.Instant {
147
+ return Temporal.Instant.fromEpochMilliseconds(date.getTime());
148
+ }
149
+
150
+ // =============================================================================
151
+ // Duration parsing
152
+ // =============================================================================
153
+
154
+ /**
155
+ * Parse an ISO 8601 duration string to Temporal.Duration
156
+ *
157
+ * @example
158
+ * parseDuration('PT1H30M')
159
+ * // → Temporal.Duration (1 hour 30 minutes)
160
+ */
161
+ export function parseDuration(iso: string): Temporal.Duration {
162
+ return Temporal.Duration.from(iso);
163
+ }
164
+
165
+ /**
166
+ * Parse an ISO 8601 duration string to Temporal.Duration, or undefined if input is nullish
167
+ */
168
+ export function parseDurationOrUndefined(iso: string | null | undefined): Temporal.Duration | undefined {
169
+ return iso ? Temporal.Duration.from(iso) : undefined;
170
+ }
@@ -0,0 +1,90 @@
1
+ /**
2
+ * Generic serialization utilities for Temporal types
3
+ *
4
+ * Provides a unified way to convert any Temporal type to its ISO string representation.
5
+ * These functions are simple wrappers around `.toString()` but provide:
6
+ * - Type safety
7
+ * - Consistent naming
8
+ * - Optional value handling
9
+ * - A single import point
10
+ *
11
+ * ## API Boundary Pattern
12
+ *
13
+ * When sending Temporal types to API endpoints, use this composition:
14
+ *
15
+ * ```typescript
16
+ * import { plainDateTimeToZonedDateTime, serialize } from "@sixthshift/design-system/date-time";
17
+ *
18
+ * // For PlainDateTime (timezone-naive) → API string
19
+ * const apiValue = serialize(plainDateTimeToZonedDateTime(dateTime));
20
+ * // → "2026-01-24T15:30:00-08:00[America/Los_Angeles]"
21
+ *
22
+ * // For PlainDate → API string (midnight in user's timezone)
23
+ * const apiValue = serialize(plainDateToZonedDateTime(date));
24
+ * // → "2026-01-24T00:00:00-08:00[America/Los_Angeles]"
25
+ *
26
+ * // Server receives and parses to UTC Instant
27
+ * parseInstant(apiValue) → Instant('2026-01-24T23:30:00Z')
28
+ * ```
29
+ *
30
+ */
31
+
32
+ import type { Temporal } from "@js-temporal/polyfill";
33
+
34
+ /**
35
+ * Serialize any Temporal type to its ISO 8601 string representation
36
+ *
37
+ * @example
38
+ * serialize(Temporal.PlainDate.from('2026-01-24'))
39
+ * // → '2026-01-24'
40
+ *
41
+ * serialize(Temporal.PlainDateTime.from('2026-01-24T15:30'))
42
+ * // → '2026-01-24T15:30:00'
43
+ *
44
+ * serialize(Temporal.Instant.from('2026-01-24T04:30:00Z'))
45
+ * // → '2026-01-24T04:30:00Z'
46
+ */
47
+ export function serialize(
48
+ temporal: Temporal.Instant | Temporal.PlainDate | Temporal.PlainDateTime | Temporal.PlainTime | Temporal.ZonedDateTime | Temporal.Duration
49
+ ): string {
50
+ return temporal.toString();
51
+ }
52
+
53
+ /**
54
+ * Serialize optional Temporal type to ISO string or undefined
55
+ *
56
+ * @example
57
+ * serializeOptional(undefined) // → undefined
58
+ * serializeOptional(Temporal.PlainDate.from('2026-01-24')) // → '2026-01-24'
59
+ */
60
+ export function serializeOptional(
61
+ temporal: Temporal.Instant | Temporal.PlainDate | Temporal.PlainDateTime | Temporal.PlainTime | Temporal.ZonedDateTime | Temporal.Duration | null | undefined
62
+ ): string | undefined {
63
+ return temporal?.toString();
64
+ }
65
+
66
+ // Type-specific convenience functions (optional, for clarity)
67
+
68
+ export function serializeInstant(instant: Temporal.Instant): string {
69
+ return instant.toString();
70
+ }
71
+
72
+ export function serializeDate(date: Temporal.PlainDate): string {
73
+ return date.toString();
74
+ }
75
+
76
+ export function serializeDateTime(dateTime: Temporal.PlainDateTime): string {
77
+ return dateTime.toString();
78
+ }
79
+
80
+ export function serializeTime(time: Temporal.PlainTime): string {
81
+ return time.toString();
82
+ }
83
+
84
+ export function serializeZonedDateTime(zoned: Temporal.ZonedDateTime): string {
85
+ return zoned.toString();
86
+ }
87
+
88
+ export function serializeDuration(duration: Temporal.Duration): string {
89
+ return duration.toString();
90
+ }
@@ -0,0 +1,47 @@
1
+ /**
2
+ * Timezone conversion utilities
3
+ *
4
+ * Functions for converting between timezones and working with ZonedDateTime
5
+ */
6
+
7
+ import { Temporal } from "@js-temporal/polyfill";
8
+
9
+ /**
10
+ * Convert an instant to a zoned date/time in a specific timezone
11
+ */
12
+ export function toTimezone(instant: Temporal.Instant, timezone: string): Temporal.ZonedDateTime {
13
+ return instant.toZonedDateTimeISO(timezone);
14
+ }
15
+
16
+ /**
17
+ * Convert a zoned date/time to a different timezone
18
+ */
19
+ export function changeTimezone(zoned: Temporal.ZonedDateTime, timezone: string): Temporal.ZonedDateTime {
20
+ return zoned.withTimeZone(timezone);
21
+ }
22
+
23
+ /**
24
+ * Interpret PlainDateTime in a specific timezone, returning ZonedDateTime
25
+ *
26
+ * @example
27
+ * const dt = Temporal.PlainDateTime.from('2026-01-24T15:30');
28
+ * const zoned = plainDateTimeToZonedDateTime(dt, 'America/Los_Angeles');
29
+ * // → 2026-01-24T15:30:00-08:00[America/Los_Angeles]
30
+ */
31
+ export function plainDateTimeToZonedDateTime(dateTime: Temporal.PlainDateTime, timezone?: string): Temporal.ZonedDateTime {
32
+ const tz = timezone ?? Temporal.Now.timeZoneId();
33
+ return dateTime.toZonedDateTime(tz);
34
+ }
35
+
36
+ /**
37
+ * Interpret PlainDate as midnight in a specific timezone, returning ZonedDateTime
38
+ *
39
+ * @example
40
+ * const date = Temporal.PlainDate.from('2026-01-24');
41
+ * const zoned = plainDateToZonedDateTime(date, 'America/Los_Angeles');
42
+ * // → 2026-01-24T00:00:00-08:00[America/Los_Angeles]
43
+ */
44
+ export function plainDateToZonedDateTime(date: Temporal.PlainDate, timezone?: string): Temporal.ZonedDateTime {
45
+ const tz = timezone ?? Temporal.Now.timeZoneId();
46
+ return date.toPlainDateTime(Temporal.PlainTime.from("00:00")).toZonedDateTime(tz);
47
+ }