@charcuterie/ui 3.0.0 → 3.2.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 (130) hide show
  1. package/README.md +10 -0
  2. package/dist/Badge/Badge.d.ts +33 -3
  3. package/dist/Badge/Badge.d.ts.map +1 -1
  4. package/dist/Badge/Badge.js +20 -2
  5. package/dist/Badge/Badge.js.map +1 -1
  6. package/dist/Board/Board.d.ts +119 -0
  7. package/dist/Board/Board.d.ts.map +1 -0
  8. package/dist/Board/Board.js +165 -0
  9. package/dist/Board/Board.js.map +1 -0
  10. package/dist/Board/BoardCard.d.ts +120 -0
  11. package/dist/Board/BoardCard.d.ts.map +1 -0
  12. package/dist/Board/BoardCard.js +77 -0
  13. package/dist/Board/BoardCard.js.map +1 -0
  14. package/dist/Board/BoardLaneList.d.ts +103 -0
  15. package/dist/Board/BoardLaneList.d.ts.map +1 -0
  16. package/dist/Board/BoardLaneList.js +69 -0
  17. package/dist/Board/BoardLaneList.js.map +1 -0
  18. package/dist/Board/boardMove.d.ts +116 -0
  19. package/dist/Board/boardMove.d.ts.map +1 -0
  20. package/dist/Board/boardMove.js +116 -0
  21. package/dist/Board/boardMove.js.map +1 -0
  22. package/dist/Board/useBoardDrag.d.ts +44 -0
  23. package/dist/Board/useBoardDrag.d.ts.map +1 -0
  24. package/dist/Board/useBoardDrag.js +237 -0
  25. package/dist/Board/useBoardDrag.js.map +1 -0
  26. package/dist/Checkbox/Checkbox.d.ts.map +1 -1
  27. package/dist/Checkbox/Checkbox.js +15 -4
  28. package/dist/Checkbox/Checkbox.js.map +1 -1
  29. package/dist/DataTable/DataTable.d.ts +159 -0
  30. package/dist/DataTable/DataTable.d.ts.map +1 -0
  31. package/dist/DataTable/DataTable.js +197 -0
  32. package/dist/DataTable/DataTable.js.map +1 -0
  33. package/dist/DatePicker/DateGrid.d.ts +79 -0
  34. package/dist/DatePicker/DateGrid.d.ts.map +1 -0
  35. package/dist/DatePicker/DateGrid.js +220 -0
  36. package/dist/DatePicker/DateGrid.js.map +1 -0
  37. package/dist/DatePicker/DatePicker.d.ts +138 -0
  38. package/dist/DatePicker/DatePicker.d.ts.map +1 -0
  39. package/dist/DatePicker/DatePicker.js +384 -0
  40. package/dist/DatePicker/DatePicker.js.map +1 -0
  41. package/dist/DatePicker/datePresets.d.ts +29 -0
  42. package/dist/DatePicker/datePresets.d.ts.map +1 -0
  43. package/dist/DatePicker/datePresets.js +28 -0
  44. package/dist/DatePicker/datePresets.js.map +1 -0
  45. package/dist/DatePicker/parseDateInput.d.ts +106 -0
  46. package/dist/DatePicker/parseDateInput.d.ts.map +1 -0
  47. package/dist/DatePicker/parseDateInput.js +419 -0
  48. package/dist/DatePicker/parseDateInput.js.map +1 -0
  49. package/dist/DatePicker/plainDate.d.ts +167 -0
  50. package/dist/DatePicker/plainDate.d.ts.map +1 -0
  51. package/dist/DatePicker/plainDate.js +272 -0
  52. package/dist/DatePicker/plainDate.js.map +1 -0
  53. package/dist/Dialog/Dialog.d.ts +12 -2
  54. package/dist/Dialog/Dialog.d.ts.map +1 -1
  55. package/dist/Dialog/Dialog.js +2 -2
  56. package/dist/Dialog/Dialog.js.map +1 -1
  57. package/dist/MarkdownEditor/MarkdownEditor.d.ts +147 -0
  58. package/dist/MarkdownEditor/MarkdownEditor.d.ts.map +1 -0
  59. package/dist/MarkdownEditor/MarkdownEditor.js +492 -0
  60. package/dist/MarkdownEditor/MarkdownEditor.js.map +1 -0
  61. package/dist/MarkdownEditor/markdownCommands.d.ts +202 -0
  62. package/dist/MarkdownEditor/markdownCommands.d.ts.map +1 -0
  63. package/dist/MarkdownEditor/markdownCommands.js +379 -0
  64. package/dist/MarkdownEditor/markdownCommands.js.map +1 -0
  65. package/dist/MarkdownEditor/markdownSpans.d.ts +92 -0
  66. package/dist/MarkdownEditor/markdownSpans.d.ts.map +1 -0
  67. package/dist/MarkdownEditor/markdownSpans.js +303 -0
  68. package/dist/MarkdownEditor/markdownSpans.js.map +1 -0
  69. package/dist/Modal/Modal.d.ts +6 -2
  70. package/dist/Modal/Modal.d.ts.map +1 -1
  71. package/dist/Modal/Modal.js +1 -1
  72. package/dist/Modal/Modal.js.map +1 -1
  73. package/dist/Overlay/OverlayPanel.d.ts +13 -2
  74. package/dist/Overlay/OverlayPanel.d.ts.map +1 -1
  75. package/dist/Overlay/OverlayPanel.js +5 -1
  76. package/dist/Overlay/OverlayPanel.js.map +1 -1
  77. package/dist/categoricalStyles.d.ts +53 -0
  78. package/dist/categoricalStyles.d.ts.map +1 -0
  79. package/dist/categoricalStyles.js +195 -0
  80. package/dist/categoricalStyles.js.map +1 -0
  81. package/dist/index.d.ts +22 -0
  82. package/dist/index.d.ts.map +1 -1
  83. package/dist/index.js +11 -0
  84. package/dist/index.js.map +1 -1
  85. package/package.json +2 -2
  86. package/src/Badge/Badge.mdx +46 -0
  87. package/src/Badge/Badge.stories.tsx +98 -4
  88. package/src/Badge/Badge.test.tsx +157 -2
  89. package/src/Badge/Badge.tsx +49 -3
  90. package/src/Board/Board.mdx +199 -0
  91. package/src/Board/Board.stories.tsx +707 -0
  92. package/src/Board/Board.test.tsx +310 -0
  93. package/src/Board/Board.tsx +333 -0
  94. package/src/Board/BoardCard.tsx +290 -0
  95. package/src/Board/BoardLaneList.tsx +275 -0
  96. package/src/Board/boardMove.test.ts +213 -0
  97. package/src/Board/boardMove.ts +225 -0
  98. package/src/Board/useBoardDrag.ts +413 -0
  99. package/src/Checkbox/Checkbox.tsx +34 -3
  100. package/src/DataTable/DataTable.mdx +123 -0
  101. package/src/DataTable/DataTable.stories.tsx +411 -0
  102. package/src/DataTable/DataTable.test.tsx +386 -0
  103. package/src/DataTable/DataTable.tsx +497 -0
  104. package/src/DatePicker/DateGrid.tsx +467 -0
  105. package/src/DatePicker/DatePicker.mdx +170 -0
  106. package/src/DatePicker/DatePicker.stories.tsx +268 -0
  107. package/src/DatePicker/DatePicker.test.tsx +455 -0
  108. package/src/DatePicker/DatePicker.tsx +852 -0
  109. package/src/DatePicker/datePresets.ts +34 -0
  110. package/src/DatePicker/parseDateInput.test.ts +187 -0
  111. package/src/DatePicker/parseDateInput.ts +629 -0
  112. package/src/DatePicker/plainDate.test.ts +375 -0
  113. package/src/DatePicker/plainDate.ts +465 -0
  114. package/src/Dialog/Dialog.stories.tsx +48 -0
  115. package/src/Dialog/Dialog.test.tsx +30 -0
  116. package/src/Dialog/Dialog.tsx +13 -1
  117. package/src/MarkdownEditor/MarkdownEditor.mdx +189 -0
  118. package/src/MarkdownEditor/MarkdownEditor.stories.tsx +267 -0
  119. package/src/MarkdownEditor/MarkdownEditor.test.tsx +627 -0
  120. package/src/MarkdownEditor/MarkdownEditor.tsx +862 -0
  121. package/src/MarkdownEditor/markdownCommands.test.ts +384 -0
  122. package/src/MarkdownEditor/markdownCommands.ts +566 -0
  123. package/src/MarkdownEditor/markdownSpans.test.ts +217 -0
  124. package/src/MarkdownEditor/markdownSpans.ts +458 -0
  125. package/src/Modal/Modal.tsx +7 -1
  126. package/src/Overlay/OverlayPanel.tsx +17 -1
  127. package/src/argTypes.storyHelpers.ts +39 -0
  128. package/src/categoricalStyles.ts +244 -0
  129. package/src/index.ts +89 -0
  130. package/src/sourceRules.test.ts +52 -1
@@ -0,0 +1,375 @@
1
+ /**
2
+ * The calendar-date core, run under four timezones.
3
+ *
4
+ * **This is the test the component exists for.** A date picker whose
5
+ * value shifts by a day when the browser's zone changes is the
6
+ * classic defect, it is invisible at noon in UTC, and it is a
7
+ * one-line fix that nobody makes because nothing fails. So every
8
+ * assertion below runs under UTC, a western zone, UTC+14 and
9
+ * UTC-11 — the two extremes of the offset range, which straddle the
10
+ * date line — and every answer has to be identical in all four.
11
+ *
12
+ * `process.env.TZ` is genuinely re-read by Node's `Date` and `Intl`
13
+ * (the tz cache is invalidated on assignment), so this is the real
14
+ * thing rather than a stub of it.
15
+ *
16
+ * Nothing here reads the system clock: the rule against `new Date()`
17
+ * with no argument is what stops a date suite from passing on the
18
+ * day it was written and rotting quietly afterwards.
19
+ */
20
+
21
+ import { afterAll, describe, expect, test } from "vitest"
22
+
23
+ import {
24
+ addDays,
25
+ addMonths,
26
+ clampPlainDate,
27
+ comparePlainDates,
28
+ formatPlainDate,
29
+ getDayNumber,
30
+ getDaysBetween,
31
+ getDaysInMonth,
32
+ getFirstDayOfWeek,
33
+ getIsLeapYear,
34
+ getIsValidPlainDate,
35
+ getIsWithinRange,
36
+ getLocalPlainDate,
37
+ getMonthNames,
38
+ getPlainDateFromDayNumber,
39
+ getWeekday,
40
+ getWeekdayNames,
41
+ parseIsoDate,
42
+ toIsoDate,
43
+ } from "./plainDate.ts"
44
+
45
+ const originalTimeZone = process.env.TZ
46
+
47
+ afterAll(() => {
48
+ process.env.TZ = originalTimeZone
49
+ })
50
+
51
+ const TIME_ZONES = [
52
+ "UTC",
53
+ "America/Denver",
54
+ // UTC+14 and UTC-11: the widest pair the offset range allows, and
55
+ // the pair that is on different calendar days for 23 hours of
56
+ // every 24.
57
+ "Pacific/Kiritimati",
58
+ "Pacific/Niue",
59
+ ]
60
+
61
+ describe.each(TIME_ZONES)("under TZ=%s", (timeZone) => {
62
+ process.env.TZ = timeZone
63
+
64
+ test("an ISO date round-trips unchanged", () => {
65
+ process.env.TZ = timeZone
66
+
67
+ const date = parseIsoDate("2026-08-19")
68
+
69
+ expect(date).toEqual({ day: 19, month: 8, year: 2026 })
70
+
71
+ expect(
72
+ toIsoDate(date ?? { day: 1, month: 1, year: 1 }),
73
+ ).toBe("2026-08-19")
74
+ })
75
+
76
+ test("formatting a plain date names the same day everywhere", () => {
77
+ process.env.TZ = timeZone
78
+
79
+ expect(
80
+ formatPlainDate(
81
+ { day: 19, month: 8, year: 2026 },
82
+ { dateStyle: "full", locale: "en-US" },
83
+ ),
84
+ ).toBe("Wednesday, August 19, 2026")
85
+
86
+ // The 1st and the 31st are where an off-by-one zone shift shows
87
+ // up as a different month, not just a different day.
88
+ expect(
89
+ formatPlainDate(
90
+ { day: 1, month: 1, year: 2026 },
91
+ { dateStyle: "full", locale: "en-US" },
92
+ ),
93
+ ).toBe("Thursday, January 1, 2026")
94
+
95
+ expect(
96
+ formatPlainDate(
97
+ { day: 31, month: 12, year: 2025 },
98
+ { dateStyle: "full", locale: "en-US" },
99
+ ),
100
+ ).toBe("Wednesday, December 31, 2025")
101
+ })
102
+
103
+ test("adding a day never lands on the same or a skipped date", () => {
104
+ process.env.TZ = timeZone
105
+
106
+ // 2026-03-08 is the US DST spring-forward. A `Date`-based
107
+ // `+ 24 * 60 * 60 * 1000` lands on the 8th again in Denver.
108
+ expect(
109
+ toIsoDate(
110
+ addDays({ day: 8, month: 3, year: 2026 }, 1),
111
+ ),
112
+ ).toBe("2026-03-09")
113
+
114
+ expect(
115
+ toIsoDate(
116
+ addDays({ day: 1, month: 11, year: 2026 }, 1),
117
+ ),
118
+ ).toBe("2026-11-02")
119
+
120
+ expect(
121
+ toIsoDate(
122
+ addDays({ day: 31, month: 12, year: 2026 }, 1),
123
+ ),
124
+ ).toBe("2027-01-01")
125
+ })
126
+
127
+ test("the weekday of a fixed date does not move", () => {
128
+ process.env.TZ = timeZone
129
+
130
+ expect(
131
+ getWeekday({ day: 19, month: 8, year: 2026 }),
132
+ ).toBe(3)
133
+
134
+ expect(
135
+ getWeekday({ day: 16, month: 8, year: 2026 }),
136
+ ).toBe(0)
137
+ })
138
+ })
139
+
140
+ process.env.TZ = "UTC"
141
+
142
+ test("the day number round-trips across four centuries", () => {
143
+ // Every day from 1899 to 2101, including all leap-year and
144
+ // century-rule boundaries, through both directions of Hinnant's
145
+ // algorithm.
146
+ const from = getDayNumber({
147
+ day: 1,
148
+ month: 1,
149
+ year: 1899,
150
+ })
151
+
152
+ const to = getDayNumber({
153
+ day: 31,
154
+ month: 12,
155
+ year: 2101,
156
+ })
157
+
158
+ for (
159
+ let dayNumber = from;
160
+ dayNumber <= to;
161
+ dayNumber += 1
162
+ ) {
163
+ expect(
164
+ getDayNumber(getPlainDateFromDayNumber(dayNumber)),
165
+ ).toBe(dayNumber)
166
+ }
167
+
168
+ expect(
169
+ getDayNumber({ day: 1, month: 1, year: 1970 }),
170
+ ).toBe(0)
171
+ })
172
+
173
+ test("the leap rule includes the century exceptions", () => {
174
+ expect(getIsLeapYear(2024)).toBe(true)
175
+ expect(getIsLeapYear(2026)).toBe(false)
176
+ expect(getIsLeapYear(1900)).toBe(false)
177
+ expect(getIsLeapYear(2000)).toBe(true)
178
+
179
+ expect(getDaysInMonth(2024, 2)).toBe(29)
180
+ expect(getDaysInMonth(1900, 2)).toBe(28)
181
+ expect(getDaysInMonth(2026, 2)).toBe(28)
182
+ })
183
+
184
+ test("a month step clamps the day instead of overflowing", () => {
185
+ // `new Date(2026, 0, 31)` with `setMonth(1)` is 3 March. One month
186
+ // after 31 January is 28 February.
187
+ expect(
188
+ toIsoDate(
189
+ addMonths({ day: 31, month: 1, year: 2026 }, 1),
190
+ ),
191
+ ).toBe("2026-02-28")
192
+
193
+ expect(
194
+ toIsoDate(
195
+ addMonths({ day: 31, month: 1, year: 2024 }, 1),
196
+ ),
197
+ ).toBe("2024-02-29")
198
+
199
+ expect(
200
+ toIsoDate(
201
+ addMonths({ day: 15, month: 11, year: 2026 }, 3),
202
+ ),
203
+ ).toBe("2027-02-15")
204
+
205
+ expect(
206
+ toIsoDate(
207
+ addMonths({ day: 15, month: 2, year: 2026 }, -3),
208
+ ),
209
+ ).toBe("2025-11-15")
210
+ })
211
+
212
+ test("an impossible date is rejected rather than rolled forward", () => {
213
+ expect(parseIsoDate("2026-02-30")).toBeNull()
214
+ expect(parseIsoDate("2026-13-01")).toBeNull()
215
+ expect(parseIsoDate("2026-00-10")).toBeNull()
216
+ expect(parseIsoDate("19/8/2026")).toBeNull()
217
+ expect(parseIsoDate("2026-8-19")).toBeNull()
218
+ expect(parseIsoDate("")).toBeNull()
219
+ expect(parseIsoDate(null)).toBeNull()
220
+
221
+ expect(
222
+ getIsValidPlainDate({ day: 29, month: 2, year: 2026 }),
223
+ ).toBe(false)
224
+
225
+ expect(
226
+ getIsValidPlainDate({ day: 29, month: 2, year: 2024 }),
227
+ ).toBe(true)
228
+ })
229
+
230
+ test("day counting is what a staleness threshold needs", () => {
231
+ // Docket marks a Todo stale after N days. Across a DST boundary a
232
+ // millisecond subtraction gives 13.958 days and `Math.floor` turns
233
+ // a fortnight into thirteen days.
234
+ expect(
235
+ getDaysBetween(
236
+ { day: 1, month: 3, year: 2026 },
237
+ { day: 15, month: 3, year: 2026 },
238
+ ),
239
+ ).toBe(14)
240
+
241
+ expect(
242
+ getDaysBetween(
243
+ { day: 19, month: 8, year: 2026 },
244
+ { day: 19, month: 8, year: 2026 },
245
+ ),
246
+ ).toBe(0)
247
+
248
+ expect(
249
+ getDaysBetween(
250
+ { day: 19, month: 8, year: 2026 },
251
+ { day: 12, month: 8, year: 2026 },
252
+ ),
253
+ ).toBe(-7)
254
+ })
255
+
256
+ test("clamping and range checks agree at the boundaries", () => {
257
+ const minimum = { day: 10, month: 8, year: 2026 }
258
+
259
+ const maximum = { day: 20, month: 8, year: 2026 }
260
+
261
+ expect(
262
+ toIsoDate(
263
+ clampPlainDate(
264
+ { day: 1, month: 8, year: 2026 },
265
+ minimum,
266
+ maximum,
267
+ ),
268
+ ),
269
+ ).toBe("2026-08-10")
270
+
271
+ expect(
272
+ toIsoDate(
273
+ clampPlainDate(
274
+ { day: 31, month: 8, year: 2026 },
275
+ minimum,
276
+ maximum,
277
+ ),
278
+ ),
279
+ ).toBe("2026-08-20")
280
+
281
+ expect(getIsWithinRange(minimum, minimum, maximum)).toBe(
282
+ true,
283
+ )
284
+ expect(getIsWithinRange(maximum, minimum, maximum)).toBe(
285
+ true,
286
+ )
287
+
288
+ expect(
289
+ getIsWithinRange(
290
+ { day: 21, month: 8, year: 2026 },
291
+ minimum,
292
+ maximum,
293
+ ),
294
+ ).toBe(false)
295
+
296
+ expect(comparePlainDates(minimum, maximum)).toBeLessThan(
297
+ 0,
298
+ )
299
+ })
300
+
301
+ /**
302
+ * The whole argument in one assertion.
303
+ *
304
+ * One instant. Two zones. **Two different calendar dates** — and the
305
+ * component makes you say which one you mean, instead of silently
306
+ * using the browser's. This is the bug: store the wrong one of these
307
+ * as a due date and the task is due a day early for everybody east
308
+ * of you, forever.
309
+ */
310
+ test("an instant is two different calendar dates, and you must say which", () => {
311
+ const instant = new Date("2026-08-20T04:30:00Z")
312
+
313
+ expect(
314
+ toIsoDate(getLocalPlainDate(instant, "America/Denver")),
315
+ ).toBe("2026-08-19")
316
+
317
+ expect(toIsoDate(getLocalPlainDate(instant, "UTC"))).toBe(
318
+ "2026-08-20",
319
+ )
320
+
321
+ expect(
322
+ toIsoDate(
323
+ getLocalPlainDate(instant, "Pacific/Kiritimati"),
324
+ ),
325
+ ).toBe("2026-08-20")
326
+ })
327
+
328
+ test("the first day of the week comes from the locale", () => {
329
+ expect(getFirstDayOfWeek("en-US")).toBe(0)
330
+ expect(getFirstDayOfWeek("en-GB")).toBe(1)
331
+ expect(getFirstDayOfWeek("fr-FR")).toBe(1)
332
+ })
333
+
334
+ test("weekday and month names are rotated to the locale's own week", () => {
335
+ expect(
336
+ getWeekdayNames({
337
+ firstDayOfWeek: 0,
338
+ locale: "en-US",
339
+ weekday: "short",
340
+ })[0],
341
+ ).toBe("Sun")
342
+
343
+ expect(
344
+ getWeekdayNames({
345
+ firstDayOfWeek: 1,
346
+ locale: "en-GB",
347
+ weekday: "short",
348
+ })[0],
349
+ ).toBe("Mon")
350
+
351
+ expect(
352
+ getWeekdayNames({
353
+ firstDayOfWeek: 0,
354
+ locale: "en-US",
355
+ weekday: "long",
356
+ }),
357
+ ).toHaveLength(7)
358
+
359
+ expect(
360
+ getMonthNames({ locale: "en-US", month: "long" }),
361
+ ).toEqual([
362
+ "January",
363
+ "February",
364
+ "March",
365
+ "April",
366
+ "May",
367
+ "June",
368
+ "July",
369
+ "August",
370
+ "September",
371
+ "October",
372
+ "November",
373
+ "December",
374
+ ])
375
+ })