@sixthshift/design-system 0.2.1 → 0.3.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 (288) hide show
  1. package/README.md +135 -28
  2. package/dist/components/Badge/Badge.d.ts +5 -2
  3. package/dist/components/Badge/Badge.d.ts.map +1 -1
  4. package/dist/components/Badge/Badge.js +5 -3
  5. package/dist/components/Badge/Badge.js.map +1 -1
  6. package/dist/components/BarChart/BarChart.d.ts +8 -2
  7. package/dist/components/BarChart/BarChart.d.ts.map +1 -1
  8. package/dist/components/BarChart/BarChart.js +5 -3
  9. package/dist/components/BarChart/BarChart.js.map +1 -1
  10. package/dist/components/Calendar/Calendar.d.ts +17 -1
  11. package/dist/components/Calendar/Calendar.d.ts.map +1 -1
  12. package/dist/components/Calendar/Calendar.js +80 -225
  13. package/dist/components/Calendar/Calendar.js.map +1 -1
  14. package/dist/components/Calendar/CalendarView.d.ts +15 -0
  15. package/dist/components/Calendar/CalendarView.d.ts.map +1 -0
  16. package/dist/components/Calendar/CalendarView.js +255 -0
  17. package/dist/components/Calendar/CalendarView.js.map +1 -0
  18. package/dist/components/Calendar/calendar.types.d.ts +78 -27
  19. package/dist/components/Calendar/calendar.types.d.ts.map +1 -1
  20. package/dist/components/Calendar/calendar.types.js +10 -3
  21. package/dist/components/Calendar/calendar.types.js.map +1 -1
  22. package/dist/components/Calendar/index.d.ts +16 -2
  23. package/dist/components/Calendar/index.d.ts.map +1 -1
  24. package/dist/components/Calendar/index.js +15 -1
  25. package/dist/components/Calendar/index.js.map +1 -1
  26. package/dist/components/ColorDot/ColorDot.d.ts +9 -3
  27. package/dist/components/ColorDot/ColorDot.d.ts.map +1 -1
  28. package/dist/components/ColorDot/ColorDot.js +5 -3
  29. package/dist/components/ColorDot/ColorDot.js.map +1 -1
  30. package/dist/components/DatePicker/DatePicker.d.ts +2 -2
  31. package/dist/components/DatePicker/DatePicker.d.ts.map +1 -1
  32. package/dist/components/DatePicker/DatePicker.js +65 -10
  33. package/dist/components/DatePicker/DatePicker.js.map +1 -1
  34. package/dist/components/DatePicker/datepicker.types.d.ts +34 -36
  35. package/dist/components/DatePicker/datepicker.types.d.ts.map +1 -1
  36. package/dist/components/DatePicker/datepicker.types.js +5 -3
  37. package/dist/components/DatePicker/datepicker.types.js.map +1 -1
  38. package/dist/components/DatePicker/index.d.ts +10 -1
  39. package/dist/components/DatePicker/index.d.ts.map +1 -1
  40. package/dist/components/DatePicker/index.js +8 -0
  41. package/dist/components/DatePicker/index.js.map +1 -1
  42. package/dist/components/DateRangePicker/DateRangePicker.d.ts +3 -2
  43. package/dist/components/DateRangePicker/DateRangePicker.d.ts.map +1 -1
  44. package/dist/components/DateRangePicker/DateRangePicker.js +17 -21
  45. package/dist/components/DateRangePicker/DateRangePicker.js.map +1 -1
  46. package/dist/components/DateRangePicker/daterangepicker.types.d.ts +20 -20
  47. package/dist/components/DateRangePicker/daterangepicker.types.d.ts.map +1 -1
  48. package/dist/components/DateRangePicker/daterangepicker.types.js +6 -0
  49. package/dist/components/DateRangePicker/daterangepicker.types.js.map +1 -1
  50. package/dist/components/DateRangePicker/index.d.ts +8 -1
  51. package/dist/components/DateRangePicker/index.d.ts.map +1 -1
  52. package/dist/components/DateRangePicker/index.js +6 -0
  53. package/dist/components/DateRangePicker/index.js.map +1 -1
  54. package/dist/components/DateTimePicker/DateTimePicker.d.ts +3 -3
  55. package/dist/components/DateTimePicker/DateTimePicker.d.ts.map +1 -1
  56. package/dist/components/DateTimePicker/DateTimePicker.js +30 -12
  57. package/dist/components/DateTimePicker/DateTimePicker.js.map +1 -1
  58. package/dist/components/DateTimePicker/datetimepicker.types.d.ts +20 -17
  59. package/dist/components/DateTimePicker/datetimepicker.types.d.ts.map +1 -1
  60. package/dist/components/DateTimePicker/datetimepicker.types.js +10 -0
  61. package/dist/components/DateTimePicker/datetimepicker.types.js.map +1 -1
  62. package/dist/components/DateTimePicker/index.d.ts +8 -1
  63. package/dist/components/DateTimePicker/index.d.ts.map +1 -1
  64. package/dist/components/DateTimePicker/index.js +6 -0
  65. package/dist/components/DateTimePicker/index.js.map +1 -1
  66. package/dist/components/DateTimeRangePicker/DateTimeRangePicker.d.ts +3 -3
  67. package/dist/components/DateTimeRangePicker/DateTimeRangePicker.d.ts.map +1 -1
  68. package/dist/components/DateTimeRangePicker/DateTimeRangePicker.js +41 -20
  69. package/dist/components/DateTimeRangePicker/DateTimeRangePicker.js.map +1 -1
  70. package/dist/components/DateTimeRangePicker/datetimerangepicker.types.d.ts +22 -18
  71. package/dist/components/DateTimeRangePicker/datetimerangepicker.types.d.ts.map +1 -1
  72. package/dist/components/DateTimeRangePicker/datetimerangepicker.types.js +8 -0
  73. package/dist/components/DateTimeRangePicker/datetimerangepicker.types.js.map +1 -1
  74. package/dist/components/DateTimeRangePicker/index.d.ts +8 -0
  75. package/dist/components/DateTimeRangePicker/index.d.ts.map +1 -1
  76. package/dist/components/DateTimeRangePicker/index.js +7 -0
  77. package/dist/components/DateTimeRangePicker/index.js.map +1 -1
  78. package/dist/components/EmptyState/EmptyState.d.ts +16 -5
  79. package/dist/components/EmptyState/EmptyState.d.ts.map +1 -1
  80. package/dist/components/EmptyState/EmptyState.js +5 -3
  81. package/dist/components/EmptyState/EmptyState.js.map +1 -1
  82. package/dist/components/Field/Field.d.ts +3 -3
  83. package/dist/components/Field/Field.d.ts.map +1 -1
  84. package/dist/components/Field/Field.js +6 -4
  85. package/dist/components/Field/Field.js.map +1 -1
  86. package/dist/components/LineChart/LineChart.d.ts +18 -2
  87. package/dist/components/LineChart/LineChart.d.ts.map +1 -1
  88. package/dist/components/LineChart/LineChart.js +5 -3
  89. package/dist/components/LineChart/LineChart.js.map +1 -1
  90. package/dist/components/Markdown/Markdown.d.ts +2 -4
  91. package/dist/components/Markdown/Markdown.d.ts.map +1 -1
  92. package/dist/components/Markdown/Markdown.js +3 -2
  93. package/dist/components/Markdown/Markdown.js.map +1 -1
  94. package/dist/components/Message/Message.d.ts.map +1 -1
  95. package/dist/components/Message/Message.js +16 -1
  96. package/dist/components/Message/Message.js.map +1 -1
  97. package/dist/components/MetricList/MetricList.d.ts +3 -3
  98. package/dist/components/MetricList/MetricList.d.ts.map +1 -1
  99. package/dist/components/MetricList/MetricList.js +5 -3
  100. package/dist/components/MetricList/MetricList.js.map +1 -1
  101. package/dist/components/Modal/Modal.d.ts +24 -5
  102. package/dist/components/Modal/Modal.d.ts.map +1 -1
  103. package/dist/components/Modal/Modal.js +16 -6
  104. package/dist/components/Modal/Modal.js.map +1 -1
  105. package/dist/components/Modal/components/ModalContext.d.ts +14 -0
  106. package/dist/components/Modal/components/ModalContext.d.ts.map +1 -1
  107. package/dist/components/Modal/components/ModalContext.js.map +1 -1
  108. package/dist/components/Modal/components/ModalHeader.d.ts.map +1 -1
  109. package/dist/components/Modal/components/ModalHeader.js +13 -2
  110. package/dist/components/Modal/components/ModalHeader.js.map +1 -1
  111. package/dist/components/NavBottom/NavBottom.d.ts +2 -1
  112. package/dist/components/NavBottom/NavBottom.d.ts.map +1 -1
  113. package/dist/components/NavBottom/NavBottom.js +6 -4
  114. package/dist/components/NavBottom/NavBottom.js.map +1 -1
  115. package/dist/components/NavSide/NavItem.js +1 -1
  116. package/dist/components/NavSide/NavItem.js.map +1 -1
  117. package/dist/components/NavSide/NavSide.d.ts +2 -1
  118. package/dist/components/NavSide/NavSide.d.ts.map +1 -1
  119. package/dist/components/NavSide/NavSide.js +5 -3
  120. package/dist/components/NavSide/NavSide.js.map +1 -1
  121. package/dist/components/Popover/Popover.d.ts.map +1 -1
  122. package/dist/components/Popover/Popover.js +7 -0
  123. package/dist/components/Popover/Popover.js.map +1 -1
  124. package/dist/components/Popover/components/PopoverBody.d.ts.map +1 -1
  125. package/dist/components/Popover/components/PopoverBody.js +5 -2
  126. package/dist/components/Popover/components/PopoverBody.js.map +1 -1
  127. package/dist/components/Popover/components/PopoverContext.d.ts +1 -0
  128. package/dist/components/Popover/components/PopoverContext.d.ts.map +1 -1
  129. package/dist/components/Popover/components/PopoverContext.js.map +1 -1
  130. package/dist/components/Popover/components/PopoverTrigger.d.ts.map +1 -1
  131. package/dist/components/Popover/components/PopoverTrigger.js +2 -2
  132. package/dist/components/Popover/components/PopoverTrigger.js.map +1 -1
  133. package/dist/components/ProgressBar/ProgressBar.d.ts +2 -1
  134. package/dist/components/ProgressBar/ProgressBar.d.ts.map +1 -1
  135. package/dist/components/ProgressBar/ProgressBar.js +5 -3
  136. package/dist/components/ProgressBar/ProgressBar.js.map +1 -1
  137. package/dist/components/Select/Select.d.ts +11 -2
  138. package/dist/components/Select/Select.d.ts.map +1 -1
  139. package/dist/components/Select/Select.js +64 -18
  140. package/dist/components/Select/Select.js.map +1 -1
  141. package/dist/components/Select/SelectDropdown.d.ts +14 -2
  142. package/dist/components/Select/SelectDropdown.d.ts.map +1 -1
  143. package/dist/components/Select/SelectDropdown.js +19 -4
  144. package/dist/components/Select/SelectDropdown.js.map +1 -1
  145. package/dist/components/Select/SelectTrigger.d.ts +10 -3
  146. package/dist/components/Select/SelectTrigger.d.ts.map +1 -1
  147. package/dist/components/Select/SelectTrigger.js +12 -3
  148. package/dist/components/Select/SelectTrigger.js.map +1 -1
  149. package/dist/components/Select/useSelectKeyboard.d.ts +15 -1
  150. package/dist/components/Select/useSelectKeyboard.d.ts.map +1 -1
  151. package/dist/components/Select/useSelectKeyboard.js +73 -65
  152. package/dist/components/Select/useSelectKeyboard.js.map +1 -1
  153. package/dist/components/Sheet/Sheet.d.ts +19 -5
  154. package/dist/components/Sheet/Sheet.d.ts.map +1 -1
  155. package/dist/components/Sheet/Sheet.js +18 -6
  156. package/dist/components/Sheet/Sheet.js.map +1 -1
  157. package/dist/components/Sheet/components/SheetContext.d.ts +10 -0
  158. package/dist/components/Sheet/components/SheetContext.d.ts.map +1 -1
  159. package/dist/components/Sheet/components/SheetContext.js.map +1 -1
  160. package/dist/components/Sheet/components/SheetHeader.d.ts.map +1 -1
  161. package/dist/components/Sheet/components/SheetHeader.js +11 -2
  162. package/dist/components/Sheet/components/SheetHeader.js.map +1 -1
  163. package/dist/components/Skeleton/Skeleton.d.ts +2 -1
  164. package/dist/components/Skeleton/Skeleton.d.ts.map +1 -1
  165. package/dist/components/Skeleton/Skeleton.js +5 -3
  166. package/dist/components/Skeleton/Skeleton.js.map +1 -1
  167. package/dist/components/Sparkline/Sparkline.d.ts +10 -2
  168. package/dist/components/Sparkline/Sparkline.d.ts.map +1 -1
  169. package/dist/components/Sparkline/Sparkline.js +5 -3
  170. package/dist/components/Sparkline/Sparkline.js.map +1 -1
  171. package/dist/components/StatsCard/StatsCard.d.ts +4 -4
  172. package/dist/components/StatsCard/StatsCard.d.ts.map +1 -1
  173. package/dist/components/StatsCard/StatsCard.js +5 -3
  174. package/dist/components/StatsCard/StatsCard.js.map +1 -1
  175. package/dist/components/Tabs/Tabs.d.ts +2 -2
  176. package/dist/components/Tabs/components/TabsList.d.ts +1 -1
  177. package/dist/components/Tabs/components/TabsList.d.ts.map +1 -1
  178. package/dist/components/Tabs/components/TabsList.js +5 -4
  179. package/dist/components/Tabs/components/TabsList.js.map +1 -1
  180. package/dist/components/Tabs/components/TabsPanels.d.ts +1 -1
  181. package/dist/components/Tabs/components/TabsPanels.d.ts.map +1 -1
  182. package/dist/components/Tabs/components/TabsPanels.js +5 -3
  183. package/dist/components/Tabs/components/TabsPanels.js.map +1 -1
  184. package/dist/components/TagChip/TagChip.d.ts +2 -1
  185. package/dist/components/TagChip/TagChip.d.ts.map +1 -1
  186. package/dist/components/TagChip/TagChip.js +5 -3
  187. package/dist/components/TagChip/TagChip.js.map +1 -1
  188. package/dist/components/TagInput/TagInput.d.ts +1 -1
  189. package/dist/components/TagInput/TagInput.d.ts.map +1 -1
  190. package/dist/components/TagInput/TagInput.js +5 -4
  191. package/dist/components/TagInput/TagInput.js.map +1 -1
  192. package/dist/components/TimePicker/TimePicker.d.ts +2 -2
  193. package/dist/components/TimePicker/TimePicker.d.ts.map +1 -1
  194. package/dist/components/TimePicker/TimePicker.js +27 -11
  195. package/dist/components/TimePicker/TimePicker.js.map +1 -1
  196. package/dist/components/TimePicker/timepicker.types.d.ts +14 -13
  197. package/dist/components/TimePicker/timepicker.types.d.ts.map +1 -1
  198. package/dist/components/TimePicker/timepicker.types.js +4 -3
  199. package/dist/components/TimePicker/timepicker.types.js.map +1 -1
  200. package/dist/date-time/index.d.ts +2 -0
  201. package/dist/date-time/index.d.ts.map +1 -1
  202. package/dist/date-time/index.js +9 -0
  203. package/dist/date-time/index.js.map +1 -1
  204. package/dist/date-time/iso.d.ts +261 -0
  205. package/dist/date-time/iso.d.ts.map +1 -0
  206. package/dist/date-time/iso.js +358 -0
  207. package/dist/date-time/iso.js.map +1 -0
  208. package/dist/date-time/now.d.ts +7 -6
  209. package/dist/date-time/now.d.ts.map +1 -1
  210. package/dist/date-time/now.js +3 -3
  211. package/dist/date-time/now.js.map +1 -1
  212. package/dist/theme/index.d.ts +4 -3
  213. package/dist/theme/index.d.ts.map +1 -1
  214. package/dist/theme/index.js +3 -2
  215. package/dist/theme/index.js.map +1 -1
  216. package/dist/theme/schema.d.ts +70 -32
  217. package/dist/theme/schema.d.ts.map +1 -1
  218. package/dist/theme/schema.js +36 -18
  219. package/dist/theme/schema.js.map +1 -1
  220. package/package.json +9 -15
  221. package/src/components/Badge/Badge.tsx +5 -4
  222. package/src/components/BarChart/BarChart.tsx +25 -30
  223. package/src/components/Calendar/Calendar.tsx +91 -401
  224. package/src/components/Calendar/CalendarView.tsx +445 -0
  225. package/src/components/Calendar/calendar.types.ts +94 -29
  226. package/src/components/Calendar/index.ts +16 -17
  227. package/src/components/ColorDot/ColorDot.tsx +6 -4
  228. package/src/components/DatePicker/DatePicker.tsx +92 -16
  229. package/src/components/DatePicker/datepicker.types.ts +38 -39
  230. package/src/components/DatePicker/index.ts +11 -10
  231. package/src/components/DateRangePicker/DateRangePicker.tsx +17 -21
  232. package/src/components/DateRangePicker/daterangepicker.types.ts +22 -22
  233. package/src/components/DateRangePicker/index.ts +9 -1
  234. package/src/components/DateTimePicker/DateTimePicker.tsx +46 -18
  235. package/src/components/DateTimePicker/datetimepicker.types.ts +22 -18
  236. package/src/components/DateTimePicker/index.ts +9 -1
  237. package/src/components/DateTimeRangePicker/DateTimeRangePicker.tsx +62 -28
  238. package/src/components/DateTimeRangePicker/datetimerangepicker.types.ts +24 -19
  239. package/src/components/DateTimeRangePicker/index.ts +9 -0
  240. package/src/components/EmptyState/EmptyState.tsx +8 -7
  241. package/src/components/Field/Field.tsx +7 -6
  242. package/src/components/LineChart/LineChart.tsx +147 -141
  243. package/src/components/Markdown/Markdown.tsx +4 -3
  244. package/src/components/Message/Message.tsx +17 -1
  245. package/src/components/MetricList/MetricList.tsx +10 -5
  246. package/src/components/Modal/Modal.tsx +119 -87
  247. package/src/components/Modal/components/ModalContext.tsx +14 -0
  248. package/src/components/Modal/components/ModalHeader.tsx +14 -2
  249. package/src/components/NavBottom/NavBottom.tsx +6 -4
  250. package/src/components/NavSide/NavItem.tsx +1 -1
  251. package/src/components/NavSide/NavSide.tsx +5 -3
  252. package/src/components/Popover/Popover.tsx +7 -0
  253. package/src/components/Popover/components/PopoverBody.tsx +6 -1
  254. package/src/components/Popover/components/PopoverContext.tsx +1 -0
  255. package/src/components/Popover/components/PopoverTrigger.tsx +2 -1
  256. package/src/components/ProgressBar/ProgressBar.tsx +32 -28
  257. package/src/components/Select/Select.tsx +76 -19
  258. package/src/components/Select/SelectDropdown.tsx +32 -3
  259. package/src/components/Select/SelectTrigger.tsx +26 -3
  260. package/src/components/Select/useSelectKeyboard.ts +20 -10
  261. package/src/components/Sheet/Sheet.tsx +108 -83
  262. package/src/components/Sheet/components/SheetContext.tsx +10 -0
  263. package/src/components/Sheet/components/SheetHeader.tsx +17 -2
  264. package/src/components/Skeleton/Skeleton.tsx +5 -3
  265. package/src/components/Sparkline/Sparkline.tsx +29 -36
  266. package/src/components/StatsCard/StatsCard.tsx +7 -6
  267. package/src/components/Tabs/components/TabsList.tsx +5 -3
  268. package/src/components/Tabs/components/TabsPanels.tsx +5 -3
  269. package/src/components/TagChip/TagChip.tsx +10 -3
  270. package/src/components/TagInput/TagInput.tsx +5 -3
  271. package/src/components/TimePicker/TimePicker.tsx +31 -13
  272. package/src/components/TimePicker/timepicker.types.ts +14 -13
  273. package/src/date-time/index.ts +61 -0
  274. package/src/date-time/iso.ts +511 -0
  275. package/src/date-time/now.ts +10 -9
  276. package/src/theme/index.ts +11 -3
  277. package/src/theme/schema.ts +82 -54
  278. package/src/theme/tokens.css +807 -0
  279. package/dist/styles.css +0 -2
  280. package/dist/theme.generated.css +0 -438
  281. package/src/styles/base.css +0 -41
  282. package/src/theme/palette.json +0 -100
  283. package/src/theme/palette.schema.json +0 -40
  284. package/src/theme/theme.json +0 -349
  285. package/src/theme/theme.schema.json +0 -876
  286. package/src/theme/typography.json +0 -7
  287. package/src/theme/typography.schema.json +0 -36
  288. package/tailwind.config.ts +0 -261
@@ -1,4 +1,4 @@
1
- import { useEffect } from "react";
1
+ import { useCallback } from "react";
2
2
  import type { SelectOption } from "./Select";
3
3
 
4
4
  interface UseSelectKeyboardOptions<T extends string> {
@@ -14,6 +14,20 @@ interface UseSelectKeyboardOptions<T extends string> {
14
14
  focusTrigger: () => void;
15
15
  }
16
16
 
17
+ /**
18
+ * Returns the keydown handler for an open Select. It is attached to the element
19
+ * that actually holds focus — the listbox, or the search input in searchable
20
+ * mode — rather than to `document`.
21
+ *
22
+ * A document-level listener fired for every keystroke on the page while any
23
+ * Select was open, which meant a Select could swallow Escape or an arrow key
24
+ * from an overlay stacked above it, and two open Selects raced each other. It
25
+ * also decoupled key handling from focus, so nothing guaranteed the element the
26
+ * user was typing into was the one being navigated. Scoping the handler to the
27
+ * focused element makes both problems structurally impossible and is what lets
28
+ * `aria-activedescendant` mean something: the focused element owns the active
29
+ * option.
30
+ */
17
31
  export function useSelectKeyboard<T extends string>({
18
32
  open,
19
33
  searchable,
@@ -26,8 +40,8 @@ export function useSelectKeyboard<T extends string>({
26
40
  handleSelect,
27
41
  focusTrigger,
28
42
  }: UseSelectKeyboardOptions<T>) {
29
- useEffect(() => {
30
- const handleKeyDown = (event: KeyboardEvent) => {
43
+ return useCallback(
44
+ (event: React.KeyboardEvent<HTMLElement>) => {
31
45
  if (!open) return;
32
46
 
33
47
  const isTyping = searchable && event.key.length === 1 && !event.ctrlKey && !event.metaKey && !event.altKey;
@@ -101,11 +115,7 @@ export function useSelectKeyboard<T extends string>({
101
115
  }
102
116
  }
103
117
  }
104
- };
105
-
106
- if (open) {
107
- document.addEventListener("keydown", handleKeyDown);
108
- return () => document.removeEventListener("keydown", handleKeyDown);
109
- }
110
- }, [open, searchable, searchValue, highlightedIndex, displayOptions, setOpen, setHighlightedIndex, setSearchValue, handleSelect, focusTrigger]);
118
+ },
119
+ [open, searchable, searchValue, highlightedIndex, displayOptions, setOpen, setHighlightedIndex, setSearchValue, handleSelect, focusTrigger]
120
+ );
111
121
  }
@@ -1,14 +1,14 @@
1
1
  import { FloatingFocusManager, useDismiss, useFloating, useInteractions, useRole } from "@floating-ui/react";
2
2
  import { useMergedFloatingRef, usePresence } from "@sixthshift/design-system/hooks";
3
3
  import { cn } from "@sixthshift/design-system/utils";
4
- import { type HTMLAttributes, type ReactNode, useCallback, useEffect, useMemo } from "react";
4
+ import { forwardRef, type HTMLAttributes, type ReactNode, useCallback, useEffect, useId, useMemo, useState } from "react";
5
5
  import { SheetBody, SheetContext, SheetFooter, SheetHeader } from "./components";
6
6
 
7
7
  // =============================================================================
8
8
  // Types
9
9
  // =============================================================================
10
10
 
11
- export type SheetProps = Pick<HTMLAttributes<HTMLDivElement>, "className" | "style"> & {
11
+ export type SheetProps = Pick<HTMLAttributes<HTMLDivElement>, "className" | "style" | "aria-label" | "aria-labelledby" | "aria-describedby"> & {
12
12
  /** Whether the sheet is open. Sheet owns its own mount lifecycle so exit animations can play. */
13
13
  open: boolean;
14
14
  /** Called when the sheet requests a state change (Esc, outside press, X button). */
@@ -41,88 +41,113 @@ const sizeClasses = {
41
41
  // Sheet
42
42
  // =============================================================================
43
43
 
44
- const SheetRoot = ({
45
- open,
46
- onOpenChange,
47
- side = "right",
48
- size = "md",
49
- dismissable = true,
50
- dismissOnOutsidePress = false,
51
- closable,
52
- className,
53
- style,
54
- children,
55
- }: SheetProps) => {
56
- const { ref: presenceRef, state, isMounted, show, hide } = usePresence();
57
-
58
- // Drive presence from the controlled `open` prop.
59
- // When `open` flips to false, we play the exit animation, then unmount.
60
- useEffect(() => {
61
- if (open) {
62
- show();
63
- } else if (isMounted) {
64
- hide();
65
- }
66
- }, [open, isMounted, show, hide]);
67
-
68
- const requestClose = useCallback(() => {
69
- onOpenChange(false);
70
- }, [onOpenChange]);
71
-
72
- const { refs, context } = useFloating({
73
- open,
74
- onOpenChange: (nextOpen) => {
75
- if (!nextOpen) requestClose();
44
+ const SheetRoot = forwardRef<HTMLDivElement, SheetProps>(
45
+ (
46
+ {
47
+ open,
48
+ onOpenChange,
49
+ side = "right",
50
+ size = "md",
51
+ dismissable = true,
52
+ dismissOnOutsidePress = false,
53
+ closable,
54
+ className,
55
+ style,
56
+ children,
57
+ "aria-label": ariaLabel,
58
+ "aria-labelledby": ariaLabelledBy,
59
+ "aria-describedby": ariaDescribedBy,
76
60
  },
77
- });
78
-
79
- const mergedRef = useMergedFloatingRef(refs, presenceRef);
80
-
81
- const dismiss = useDismiss(context, {
82
- enabled: dismissable,
83
- escapeKey: true,
84
- outsidePress: dismissOnOutsidePress,
85
- outsidePressEvent: "mousedown",
86
- });
87
-
88
- const role = useRole(context, { role: "dialog" });
89
-
90
- const { getFloatingProps } = useInteractions([dismiss, role]);
91
-
92
- const sheetContextValue = useMemo(() => ({ onClose: requestClose, closable }), [requestClose, closable]);
93
-
94
- if (!isMounted) return null;
95
-
96
- const isEntering = state === "entering";
97
- const isExiting = state === "exiting";
98
-
99
- return (
100
- <FloatingFocusManager context={context} modal={false}>
101
- <div
102
- ref={mergedRef}
103
- tabIndex={-1}
104
- data-state={state}
105
- data-side={side}
106
- className={cn(
107
- "fixed top-0 bottom-0 z-sheet flex flex-col overflow-hidden bg-bg-normal outline-hidden",
108
- "border-border-normal shadow-lg",
109
- side === "right" ? "right-0 border-l" : "left-0 border-r",
110
- sizeClasses[size],
111
- "max-sm:inset-x-0",
112
- isEntering && side === "right" && "animate-slide-right-in",
113
- isExiting && side === "right" && "animate-slide-right-out",
114
- isEntering && side === "left" && "animate-slide-left-in",
115
- isExiting && side === "left" && "animate-slide-left-out",
116
- className
117
- )}
118
- style={style}
119
- {...getFloatingProps({})}
120
- >
121
- <SheetContext.Provider value={sheetContextValue}>{children}</SheetContext.Provider>
122
- </div>
123
- </FloatingFocusManager>
124
- );
125
- };
61
+ ref
62
+ ) => {
63
+ const { ref: presenceRef, state, isMounted, show, hide } = usePresence();
64
+
65
+ // Drive presence from the controlled `open` prop.
66
+ // When `open` flips to false, we play the exit animation, then unmount.
67
+ useEffect(() => {
68
+ if (open) {
69
+ show();
70
+ } else if (isMounted) {
71
+ hide();
72
+ }
73
+ }, [open, isMounted, show, hide]);
74
+
75
+ const requestClose = useCallback(() => {
76
+ onOpenChange(false);
77
+ }, [onOpenChange]);
78
+
79
+ const { refs, context } = useFloating({
80
+ open,
81
+ onOpenChange: (nextOpen) => {
82
+ if (!nextOpen) requestClose();
83
+ },
84
+ });
85
+
86
+ const mergedRef = useMergedFloatingRef<HTMLDivElement>(refs, presenceRef, ref);
87
+
88
+ const dismiss = useDismiss(context, {
89
+ enabled: dismissable,
90
+ escapeKey: true,
91
+ outsidePress: dismissOnOutsidePress,
92
+ outsidePressEvent: "mousedown",
93
+ });
94
+
95
+ const role = useRole(context, { role: "dialog" });
96
+
97
+ const { getFloatingProps } = useInteractions([dismiss, role]);
98
+
99
+ // Named from its header, same contract as Modal — see SheetContext.
100
+ const titleId = useId();
101
+ const [hasTitle, setHasTitle] = useState(false);
102
+ const registerTitle = useCallback((present: boolean) => setHasTitle(present), []);
103
+
104
+ // `aria-labelledby` beats `aria-label` in the name computation, so a caller
105
+ // who passes a label must not also be pointed at the header — they would
106
+ // never see their own string win.
107
+ const labelledBy = ariaLabelledBy ?? (ariaLabel || !hasTitle ? undefined : titleId);
108
+
109
+ const sheetContextValue = useMemo(() => ({ onClose: requestClose, closable, titleId, registerTitle }), [requestClose, closable, titleId, registerTitle]);
110
+
111
+ if (!isMounted) return null;
112
+
113
+ const isEntering = state === "entering";
114
+ const isExiting = state === "exiting";
115
+
116
+ return (
117
+ <FloatingFocusManager context={context} modal={false}>
118
+ <div
119
+ ref={mergedRef}
120
+ // Explicit, though `getFloatingProps` also supplies it from `useRole`:
121
+ // the ARIA attributes below are only valid on a dialog, and static
122
+ // analysis cannot see a role that arrives through a spread. Matches Modal.
123
+ role="dialog"
124
+ aria-label={ariaLabel}
125
+ aria-labelledby={labelledBy}
126
+ aria-describedby={ariaDescribedBy}
127
+ tabIndex={-1}
128
+ data-state={state}
129
+ data-side={side}
130
+ className={cn(
131
+ "fixed top-0 bottom-0 z-sheet flex flex-col overflow-hidden bg-bg-normal outline-hidden",
132
+ "border-border-normal shadow-lg",
133
+ side === "right" ? "right-0 border-l" : "left-0 border-r",
134
+ sizeClasses[size],
135
+ "max-sm:inset-x-0",
136
+ isEntering && side === "right" && "animate-slide-right-in",
137
+ isExiting && side === "right" && "animate-slide-right-out",
138
+ isEntering && side === "left" && "animate-slide-left-in",
139
+ isExiting && side === "left" && "animate-slide-left-out",
140
+ className
141
+ )}
142
+ style={style}
143
+ {...getFloatingProps({})}
144
+ >
145
+ <SheetContext.Provider value={sheetContextValue}>{children}</SheetContext.Provider>
146
+ </div>
147
+ </FloatingFocusManager>
148
+ );
149
+ }
150
+ );
126
151
  SheetRoot.displayName = "Sheet";
127
152
 
128
153
  // =============================================================================
@@ -5,6 +5,16 @@ export type OnClose = (event?: MouseEvent<Element> | KeyboardEvent<Element>) =>
5
5
  interface SheetContextValue {
6
6
  onClose: OnClose;
7
7
  closable?: boolean | undefined;
8
+ /**
9
+ * The id `Sheet.Header` stamps on itself so the dialog can name itself from
10
+ * its own title instead of being announced as a bare "dialog".
11
+ */
12
+ titleId?: string;
13
+ /**
14
+ * The header reports its presence — a headerless sheet must not point
15
+ * `aria-labelledby` at an element that never rendered.
16
+ */
17
+ registerTitle?: (present: boolean) => void;
8
18
  }
9
19
 
10
20
  export const SheetContext = createContext<SheetContextValue | null>(null);
@@ -5,13 +5,28 @@ import { SheetContext } from "./SheetContext";
5
5
 
6
6
  export type SheetHeaderProps = React.HTMLAttributes<HTMLDivElement>;
7
7
 
8
- export const SheetHeader = React.forwardRef<HTMLDivElement, SheetHeaderProps>(({ className, children, ...props }, ref) => {
8
+ export const SheetHeader = React.forwardRef<HTMLDivElement, SheetHeaderProps>(({ className, children, id, ...props }, ref) => {
9
9
  const context = React.useContext(SheetContext);
10
10
  const closable = context?.closable;
11
11
  const onClose = context?.onClose;
12
+ const registerTitle = context?.registerTitle;
13
+
14
+ // Report the title to the sheet so it can name itself, and withdraw it on
15
+ // unmount. A caller-supplied `id` wins over the generated one — which the
16
+ // sheet can no longer resolve, so it is told there is no title to point at.
17
+ const ownsTitleId = id === undefined;
18
+ React.useEffect(() => {
19
+ registerTitle?.(ownsTitleId);
20
+ return () => registerTitle?.(false);
21
+ }, [registerTitle, ownsTitleId]);
12
22
 
13
23
  return (
14
- <div ref={ref} className={cn("flex flex-col space-y-1.5 border-border-normal border-b p-6", closable && "pr-12", className)} {...props}>
24
+ <div
25
+ ref={ref}
26
+ id={id ?? context?.titleId}
27
+ className={cn("flex flex-col space-y-1.5 border-border-normal border-b p-6", closable && "pr-12", className)}
28
+ {...props}
29
+ >
15
30
  {children}
16
31
  {closable && (
17
32
  <button
@@ -1,7 +1,9 @@
1
1
  import { cn } from "@sixthshift/design-system/utils";
2
+ import * as React from "react";
2
3
 
3
- const Skeleton = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => {
4
- return <div className={cn("animate-pulse rounded-md bg-bg-strong/10", className)} {...props} />;
5
- };
4
+ const Skeleton = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(({ className, ...props }, ref) => {
5
+ return <div ref={ref} className={cn("animate-pulse rounded-md bg-bg-strong/10", className)} {...props} />;
6
+ });
7
+ Skeleton.displayName = "Skeleton";
6
8
 
7
9
  export { Skeleton };
@@ -1,6 +1,6 @@
1
1
  import type { Interpolation } from "@sixthshift/design-system/line-chart";
2
2
  import { cn } from "@sixthshift/design-system/utils";
3
- import type * as React from "react";
3
+ import * as React from "react";
4
4
 
5
5
  export type SparklineProps = React.HTMLAttributes<HTMLSpanElement> & {
6
6
  data: number[];
@@ -51,43 +51,36 @@ function buildPath(points: { x: number; y: number }[], interpolation: Interpolat
51
51
  return parts.join(" ");
52
52
  }
53
53
 
54
- export const Sparkline = ({
55
- data,
56
- width = 80,
57
- height = 24,
58
- color = "var(--fg-brand)",
59
- fillArea = false,
60
- interpolation = "monotone",
61
- strokeWidth = 1.5,
62
- className,
63
- ...rest
64
- }: SparklineProps) => {
65
- if (data.length < 2) return null;
54
+ export const Sparkline = React.forwardRef<HTMLSpanElement, SparklineProps>(
55
+ ({ data, width = 80, height = 24, color = "var(--fg-brand)", fillArea = false, interpolation = "monotone", strokeWidth = 1.5, className, ...rest }, ref) => {
56
+ if (data.length < 2) return null;
66
57
 
67
- const min = Math.min(...data);
68
- const max = Math.max(...data);
69
- const range = max - min || 1;
58
+ const min = Math.min(...data);
59
+ const max = Math.max(...data);
60
+ const range = max - min || 1;
70
61
 
71
- const padY = 2;
72
- const padX = 1;
73
- const innerW = width - padX * 2;
74
- const innerH = height - padY * 2;
62
+ const padY = 2;
63
+ const padX = 1;
64
+ const innerW = width - padX * 2;
65
+ const innerH = height - padY * 2;
75
66
 
76
- const points = data.map((v, i) => ({
77
- x: padX + (i / (data.length - 1)) * innerW,
78
- y: padY + innerH - ((v - min) / range) * innerH,
79
- }));
67
+ const points = data.map((v, i) => ({
68
+ x: padX + (i / (data.length - 1)) * innerW,
69
+ y: padY + innerH - ((v - min) / range) * innerH,
70
+ }));
80
71
 
81
- const path = buildPath(points, interpolation);
72
+ const path = buildPath(points, interpolation);
82
73
 
83
- return (
84
- <span {...rest} className={cn("inline-block align-middle", className)}>
85
- <svg width={width} height={height} viewBox={`0 0 ${width} ${height}`} role="img" aria-label="Trend line">
86
- {fillArea && path && (
87
- <path d={`${path} L ${points[points.length - 1]!.x} ${height - padY} L ${points[0]!.x} ${height - padY} Z`} fill={color} opacity={0.12} />
88
- )}
89
- <path d={path} fill="none" stroke={color} strokeWidth={strokeWidth} strokeLinecap="round" strokeLinejoin="round" />
90
- </svg>
91
- </span>
92
- );
93
- };
74
+ return (
75
+ <span ref={ref} {...rest} className={cn("inline-block align-middle", className)}>
76
+ <svg width={width} height={height} viewBox={`0 0 ${width} ${height}`} role="img" aria-label="Trend line">
77
+ {fillArea && path && (
78
+ <path d={`${path} L ${points[points.length - 1]!.x} ${height - padY} L ${points[0]!.x} ${height - padY} Z`} fill={color} opacity={0.12} />
79
+ )}
80
+ <path d={path} fill="none" stroke={color} strokeWidth={strokeWidth} strokeLinecap="round" strokeLinejoin="round" />
81
+ </svg>
82
+ </span>
83
+ );
84
+ }
85
+ );
86
+ Sparkline.displayName = "Sparkline";
@@ -1,6 +1,6 @@
1
1
  import { Caption } from "@sixthshift/design-system/caption";
2
2
  import { cn } from "@sixthshift/design-system/utils";
3
- import type { ReactNode } from "react";
3
+ import * as React from "react";
4
4
 
5
5
  export type StatsCardProps = {
6
6
  /** Title of the stats card */
@@ -8,11 +8,11 @@ export type StatsCardProps = {
8
8
  /** Description text shown below the title */
9
9
  description: string;
10
10
  /** Optional icon displayed in the header */
11
- icon?: ReactNode;
11
+ icon?: React.ReactNode;
12
12
  /** Visual status indicator via left border color */
13
13
  status?: "healthy" | "warning" | "error" | "neutral";
14
14
  /** Card content */
15
- children: ReactNode;
15
+ children: React.ReactNode;
16
16
  /** Additional CSS classes */
17
17
  className?: string;
18
18
  };
@@ -24,9 +24,9 @@ const statusColors = {
24
24
  neutral: "border-l-border-subtle",
25
25
  };
26
26
 
27
- export const StatsCard = ({ title, description, icon, status = "neutral", children, className }: StatsCardProps) => {
27
+ export const StatsCard = React.forwardRef<HTMLDivElement, StatsCardProps>(({ title, description, icon, status = "neutral", children, className }, ref) => {
28
28
  return (
29
- <div className={cn("flex flex-col gap-3 rounded border border-border-subtle border-l-2 bg-bg-subtle p-4", statusColors[status], className)}>
29
+ <div ref={ref} className={cn("flex flex-col gap-3 rounded border border-border-subtle border-l-2 bg-bg-subtle p-4", statusColors[status], className)}>
30
30
  <div className="flex items-start justify-between gap-2">
31
31
  <div className="flex flex-col gap-0.5">
32
32
  <span className="font-medium text-fg-normal">{title}</span>
@@ -37,4 +37,5 @@ export const StatsCard = ({ title, description, icon, status = "neutral", childr
37
37
  <div className="flex-1">{children}</div>
38
38
  </div>
39
39
  );
40
- };
40
+ });
41
+ StatsCard.displayName = "StatsCard";
@@ -1,6 +1,6 @@
1
1
  import { cn } from "@sixthshift/design-system/utils";
2
2
  import type * as React from "react";
3
- import { useCallback, useRef } from "react";
3
+ import { forwardRef, useCallback, useRef } from "react";
4
4
  import { useTabsContext } from "./TabsContext";
5
5
  import { TabsTrigger } from "./TabsTrigger";
6
6
 
@@ -11,7 +11,7 @@ export type TabsListProps = {
11
11
  className?: string;
12
12
  };
13
13
 
14
- export const TabsList = ({ orientation = "horizontal", className }: TabsListProps) => {
14
+ export const TabsList = forwardRef<HTMLDivElement, TabsListProps>(({ orientation = "horizontal", className }, ref) => {
15
15
  const { items, value, onValueChange } = useTabsContext();
16
16
  const triggerRefs = useRef<(HTMLButtonElement | null)[]>([]);
17
17
 
@@ -78,6 +78,7 @@ export const TabsList = ({ orientation = "horizontal", className }: TabsListProp
78
78
 
79
79
  return (
80
80
  <div
81
+ ref={ref}
81
82
  role="tablist"
82
83
  aria-orientation={orientation}
83
84
  className={cn("inline-flex gap-1", orientation === "vertical" ? "flex-col" : "flex-row items-center", className)}
@@ -95,4 +96,5 @@ export const TabsList = ({ orientation = "horizontal", className }: TabsListProp
95
96
  ))}
96
97
  </div>
97
98
  );
98
- };
99
+ });
100
+ TabsList.displayName = "TabsList";
@@ -1,4 +1,5 @@
1
1
  import { cn } from "@sixthshift/design-system/utils";
2
+ import { forwardRef } from "react";
2
3
  import { useTabsContext } from "./TabsContext";
3
4
 
4
5
  export type TabsPanelsProps = {
@@ -6,7 +7,7 @@ export type TabsPanelsProps = {
6
7
  className?: string;
7
8
  };
8
9
 
9
- export const TabsPanels = ({ className }: TabsPanelsProps) => {
10
+ export const TabsPanels = forwardRef<HTMLDivElement, TabsPanelsProps>(({ className }, ref) => {
10
11
  const { items, value, baseId } = useTabsContext();
11
12
 
12
13
  const activeItem = items.find((item) => item.value === value);
@@ -19,8 +20,9 @@ export const TabsPanels = ({ className }: TabsPanelsProps) => {
19
20
  const content = typeof activeItem.content === "function" ? activeItem.content() : activeItem.content;
20
21
 
21
22
  return (
22
- <div id={panelId} role="tabpanel" aria-labelledby={triggerId} className={cn("focus-visible:outline-hidden", className)}>
23
+ <div ref={ref} id={panelId} role="tabpanel" aria-labelledby={triggerId} className={cn("focus-visible:outline-hidden", className)}>
23
24
  {content}
24
25
  </div>
25
26
  );
26
- };
27
+ });
28
+ TabsPanels.displayName = "TabsPanels";
@@ -1,6 +1,7 @@
1
1
  import { Badge } from "@sixthshift/design-system/badge";
2
2
  import { cn } from "@sixthshift/design-system/utils";
3
3
  import { X } from "lucide-react";
4
+ import * as React from "react";
4
5
 
5
6
  /**
6
7
  * One tag, rendered prettily. A `namespace:value` tag (`project:website`)
@@ -22,13 +23,18 @@ export type TagChipProps = {
22
23
  className?: string;
23
24
  };
24
25
 
25
- export const TagChip = ({ tag, onRemove, size = "sm", className }: TagChipProps) => {
26
+ export const TagChip = React.forwardRef<HTMLSpanElement, TagChipProps>(({ tag, onRemove, size = "sm", className }, ref) => {
26
27
  const sep = tag.indexOf(":");
27
28
  const namespace = sep > 0 ? tag.slice(0, sep) : null;
28
29
  const value = sep > 0 ? tag.slice(sep + 1) : tag;
29
30
 
30
31
  return (
31
- <Badge variant="outline" intent="muted" className={cn("gap-1 font-normal", size === "sm" ? "px-1.5 py-0 text-[10px]" : "px-2 py-0.5 text-xs", className)}>
32
+ <Badge
33
+ ref={ref}
34
+ variant="outline"
35
+ intent="muted"
36
+ className={cn("gap-1 font-normal", size === "sm" ? "px-1.5 py-0 text-[10px]" : "px-2 py-0.5 text-xs", className)}
37
+ >
32
38
  {namespace && <span className="text-fg-subtle">{namespace}:</span>}
33
39
  <span>{value}</span>
34
40
  {onRemove && (
@@ -43,4 +49,5 @@ export const TagChip = ({ tag, onRemove, size = "sm", className }: TagChipProps)
43
49
  )}
44
50
  </Badge>
45
51
  );
46
- };
52
+ });
53
+ TagChip.displayName = "TagChip";
@@ -1,6 +1,6 @@
1
1
  import { TagChip } from "@sixthshift/design-system/tag-chip";
2
2
  import { cn } from "@sixthshift/design-system/utils";
3
- import { type KeyboardEvent, useState } from "react";
3
+ import { forwardRef, type KeyboardEvent, useState } from "react";
4
4
 
5
5
  /**
6
6
  * Token input for tags: existing tags render as removable chips, typing plus
@@ -18,7 +18,7 @@ export type TagInputProps = {
18
18
  className?: string;
19
19
  };
20
20
 
21
- export const TagInput = ({ value, onChange, placeholder = "Add a tag…", id, className }: TagInputProps) => {
21
+ export const TagInput = forwardRef<HTMLDivElement, TagInputProps>(({ value, onChange, placeholder = "Add a tag…", id, className }, ref) => {
22
22
  const [draft, setDraft] = useState("");
23
23
 
24
24
  const commit = () => {
@@ -38,6 +38,7 @@ export const TagInput = ({ value, onChange, placeholder = "Add a tag…", id, cl
38
38
 
39
39
  return (
40
40
  <div
41
+ ref={ref}
41
42
  className={cn(
42
43
  "flex min-h-9 flex-wrap items-center gap-1.5 rounded-md border border-border-normal bg-bg-normal px-2 py-1.5 text-sm shadow-xs transition-colors focus-within:ring-2 focus-within:ring-focus-ring",
43
44
  className
@@ -57,4 +58,5 @@ export const TagInput = ({ value, onChange, placeholder = "Add a tag…", id, cl
57
58
  />
58
59
  </div>
59
60
  );
60
- };
61
+ });
62
+ TagInput.displayName = "TagInput";